/*
	Redactor v9.1.4
	Updated: Sep 10, 2013

	http://imperavi.com/redactor/

	Copyright (c) 2009-2013, Imperavi LLC.
	License: http://imperavi.com/redactor/license/

	Usage: $('#content').redactor();
*/

//if (!document.getSelection) document.getSelection = function () { return document.selection; }

(function ($) {
     var uuid = 0;
     var rtePaste = false;

     "use strict";

     var Range = function (range) {
          this[0] = range.startOffset;
          this[1] = range.endOffset;

          this.range = range;

          return this;
     };

     Range.prototype.equals = function () {
          return this[0] === this[1];
     };

     // Plugin
     $.fn.redactor = function (options) {
          var val = [];
          var args = Array.prototype.slice.call(arguments, 1);

          if (typeof options === 'string') {
               this.each(function () {
                    var instance = $.data(this, 'redactor');
                    if (typeof instance !== 'undefined' && $.isFunction(instance[options])) {
                         var methodVal = instance[options].apply(instance, args);
                         if (methodVal !== undefined && methodVal !== instance) val.push(methodVal);
                    }
                    else return $.error('No such method "' + options + '" for Redactor');
               });
          }
          else {
               this.each(function () {
                    if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options));
               });
          }

          if (val.length === 0) return this;
          else if (val.length === 1) return val[0];
          else return val;

     };

     // Initialization
     function Redactor(el, options) {
          return new Redactor.prototype.init(el, options);
     }

     $.Redactor = Redactor;
     $.Redactor.VERSION = '9.1.4';
     $.Redactor.opts = {

          // settings
          rangy: false,

          iframe: false,
          fullpage: false,
          css: false, // url

          lang: 'en',
          direction: 'ltr', // ltr or rtl

          placeholder: false,

          wym: false,
          mobile: true,
          cleanup: true,
          tidyHtml: true,
          pastePlainText: false,
          removeEmptyTags: true,
          templateVars: false,

          visual: true,
          focus: false,
          tabindex: false,
          autoresize: true,
          minHeight: false,
          shortcuts: true,

          autosave: false, // false or url
          autosaveInterval: 60, // seconds

          plugins: false, // array

          linkAnchor: false,
          linkEmail: false,
          linkProtocol: 'http://',
          linkNofollow: false,

          imageFloatMargin: '10px',
          imageGetJson: false, // url (ex. /folder/images.json ) or false

          imageUpload: false, // url
          fileUpload: false, // url
          clipboardUpload: true, // or false
          clipboardUploadUrl: false, // url
          dragUpload: true, // false

          dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false

          s3: false,
          uploadFields: false,

          observeImages: true,
          observeLinks: true,

          modalOverlay: true,

          tabSpaces: false, // true or number of spaces
          tabFocus: true,

          air: false,
          airButtons: ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent'],

          toolbar: true,
          toolbarFixed: false,
          toolbarFixedTarget: document,
          toolbarFixedTopOffset: 0, // pixels
          toolbarFixedBox: false,
          toolbarExternal: false, // ID selector
          buttonSource: true,

          buttonSeparator: '<li class="redactor_separator"></li>',

          buttonsCustom: {},
          buttonsAdd: [],
          buttons: ['html', '|', 'formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image', 'video', 'file', 'table', 'link', '|', 'alignment', '|', 'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'

          activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist', 'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
          activeButtonsStates: {
               b: 'bold',
               strong: 'bold',
               i: 'italic',
               em: 'italic',
               del: 'deleted',
               strike: 'deleted',
               ul: 'unorderedlist',
               ol: 'orderedlist',
               u: 'underline',
               tr: 'table',
               td: 'table',
               table: 'table'
          },
          activeButtonsAdd: false, // object, ex.: { tag: 'buttonName' }

          formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

          linebreaks: false,
          paragraphy: true,
          convertDivs: true,
          convertLinks: true,
          convertImageLinks: false,
          convertVideoLinks: false,
          formattingPre: false,
          phpTags: false,

          allowedTags: false,
          deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],

          boldTag: 'strong',
          italicTag: 'em',

          // private
          indentValue: 20,
          buffer: [],
          rebuffer: [],
          textareamode: false,
          emptyHtml: '<p>&#x200b;</p>',
          invisibleSpace: '&#x200b;',
          rBlockTest: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i,
          alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD',
								'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION',
								'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
          ownLine: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'],
          contOwnLine: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'],
          newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
          blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
								'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
								'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
          // lang
          langs: {
               en: {
                    html: 'HTML',
                    video: 'Insert Video',
                    image: 'Insert Image',
                    table: 'Table',
                    link: 'Link',
                    link_insert: 'Insert link',
                    link_edit: 'Edit link',
                    unlink: 'Unlink',
                    formatting: 'Formatting',
                    paragraph: 'Normal text',
                    quote: 'Quote',
                    code: 'Code',
                    header1: 'Header 1',
                    header2: 'Header 2',
                    header3: 'Header 3',
                    header4: 'Header 4',
                    header5: 'Header 5',
                    bold: 'Bold',
                    italic: 'Italic',
                    fontcolor: 'Font Color',
                    backcolor: 'Back Color',
                    unorderedlist: 'Unordered List',
                    orderedlist: 'Ordered List',
                    outdent: 'Outdent',
                    indent: 'Indent',
                    cancel: 'Cancel',
                    insert: 'Insert',
                    save: 'Save',
                    _delete: 'Delete',
                    insert_table: 'Insert Table',
                    insert_row_above: 'Add Row Above',
                    insert_row_below: 'Add Row Below',
                    insert_column_left: 'Add Column Left',
                    insert_column_right: 'Add Column Right',
                    delete_column: 'Delete Column',
                    delete_row: 'Delete Row',
                    delete_table: 'Delete Table',
                    rows: 'Rows',
                    columns: 'Columns',
                    add_head: 'Add Head',
                    delete_head: 'Delete Head',
                    title: 'Title',
                    image_position: 'Position',
                    none: 'None',
                    left: 'Left',
                    right: 'Right',
                    image_web_link: 'Image Web Link',
                    text: 'Text',
                    mailto: 'Email',
                    web: 'URL',
                    video_html_code: 'Video Embed Code',
                    file: 'Insert File',
                    upload: 'Upload',
                    download: 'Download',
                    choose: 'Choose',
                    or_choose: 'Or choose',
                    drop_file_here: 'Drop file here',
                    align_left: 'Align text to the left',
                    align_center: 'Center text',
                    align_right: 'Align text to the right',
                    align_justify: 'Justify text',
                    horizontalrule: 'Insert Horizontal Rule',
                    deleted: 'Deleted',
                    anchor: 'Anchor',
                    link_new_tab: 'Open link in new tab',
                    underline: 'Underline',
                    alignment: 'Alignment',
                    filename: 'Name (optional)',
                    edit: 'Edit'
               }
          }
     };

     // Functionality
     Redactor.fn = $.Redactor.prototype = {

          keyCode: {
               BACKSPACE: 8,
               DELETE: 46,
               DOWN: 40,
               ENTER: 13,
               ESC: 27,
               TAB: 9,
               CTRL: 17,
               META: 91,
               LEFT: 37,
               LEFT_WIN: 91
          },

          // Initialization
          init: function (el, options) {

               this.$element = this.$source = $(el);
               this.uuid = uuid++;

               // clonning options
               var opts = $.extend(true, {}, $.Redactor.opts);

               // current settings
               this.opts = $.extend(
				{},
				opts,
				this.$element.data(),
				options
			);

               this.start = true;
               this.dropdowns = [];

               // get sizes
               this.sourceHeight = this.$source.css('height');
               this.sourceWidth = this.$source.css('width');

               // dependency of the editor modes
               if (this.opts.fullpage) this.opts.iframe = true;
               if (this.opts.linebreaks) this.opts.paragraphy = false;
               if (this.opts.paragraphy) this.opts.linebreaks = false;
               if (this.opts.toolbarFixedBox) this.opts.toolbarFixed = true;

               // the alias for iframe mode
               this.document = document;
               this.window = window;

               // selection saved
               this.savedSel = false;

               // clean setup
               this.cleanlineBefore = new RegExp('^<(/?' + this.opts.ownLine.join('|/?') + '|' + this.opts.contOwnLine.join('|') + ')[ >]');
               this.cleanlineAfter = new RegExp('^<(br|/?' + this.opts.ownLine.join('|/?') + '|/' + this.opts.contOwnLine.join('|/') + ')[ >]');
               this.cleannewLevel = new RegExp('^</?(' + this.opts.newLevel.join('|') + ')[ >]');

               // block level
               this.rTestBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|') + ')$', 'i');

               // setup formatting permissions
               if (this.opts.linebreaks === false) {
                    if (this.opts.allowedTags !== false && $.inArray('p', this.opts.allowedTags) === '-1') this.opts.allowedTags.push('p');

                    if (this.opts.deniedTags !== false) {
                         var pos = $.inArray('p', this.opts.deniedTags);
                         if (pos !== '-1') this.opts.deniedTags.splice(pos, pos);
                    }
               }

               // ie & opera
               if (this.browser('msie') || this.browser('opera')) {
                    this.opts.buttons = this.removeFromArrayByValue(this.opts.buttons, 'horizontalrule');
               }

               // load lang
               this.opts.curLang = this.opts.langs[this.opts.lang];

               // Build
               this.buildStart();

          },
          initToolbar: function (lang) {
               return {
                    html:
				{
				     title: lang.html,
				     func: 'toggle'
				},
                    formatting:
				{
				     title: lang.formatting,
				     func: 'show',
				     dropdown:
					{
					     p:
						{
						     title: lang.paragraph,
						     func: 'formatBlocks'
						},
					     blockquote:
						{
						     title: lang.quote,
						     func: 'formatQuote',
						     className: 'redactor_format_blockquote'
						},
					     pre:
						{
						     title: lang.code,
						     func: 'formatBlocks',
						     className: 'redactor_format_pre'
						},
					     h1:
						{
						     title: lang.header1,
						     func: 'formatBlocks',
						     className: 'redactor_format_h1'
						},
					     h2:
						{
						     title: lang.header2,
						     func: 'formatBlocks',
						     className: 'redactor_format_h2'
						},
					     h3:
						{
						     title: lang.header3,
						     func: 'formatBlocks',
						     className: 'redactor_format_h3'
						},
					     h4:
						{
						     title: lang.header4,
						     func: 'formatBlocks',
						     className: 'redactor_format_h4'
						},
					     h5:
						{
						     title: lang.header5,
						     func: 'formatBlocks',
						     className: 'redactor_format_h5'
						}
					}
				},
                    bold:
				{
				     title: lang.bold,
				     exec: 'bold'
				},
                    italic:
				{
				     title: lang.italic,
				     exec: 'italic'
				},
                    deleted:
				{
				     title: lang.deleted,
				     exec: 'strikethrough'
				},
                    underline:
				{
				     title: lang.underline,
				     exec: 'underline'
				},
                    unorderedlist:
				{
				     title: '&bull; ' + lang.unorderedlist,
				     exec: 'insertunorderedlist'
				},
                    orderedlist:
				{
				     title: '1. ' + lang.orderedlist,
				     exec: 'insertorderedlist'
				},
                    outdent:
				{
				     title: '< ' + lang.outdent,
				     func: 'indentingOutdent'
				},
                    indent:
				{
				     title: '> ' + lang.indent,
				     func: 'indentingIndent'
				},
                    image:
				{
				     title: lang.image,
				     func: 'imageShow'
				},
                    video:
				{
				     title: lang.video,
				     func: 'videoShow'
				},
                    file:
				{
				     title: lang.file,
				     func: 'fileShow'
				},
                    table:
				{
				     title: lang.table,
				     func: 'show',
				     dropdown:
					{
					     insert_table:
						{
						     title: lang.insert_table,
						     func: 'tableShow'
						},
					     separator_drop1:
						{
						     name: 'separator'
						},
					     insert_row_above:
						{
						     title: lang.insert_row_above,
						     func: 'tableAddRowAbove'
						},
					     insert_row_below:
						{
						     title: lang.insert_row_below,
						     func: 'tableAddRowBelow'
						},
					     insert_column_left:
						{
						     title: lang.insert_column_left,
						     func: 'tableAddColumnLeft'
						},
					     insert_column_right:
						{
						     title: lang.insert_column_right,
						     func: 'tableAddColumnRight'
						},
					     separator_drop2:
						{
						     name: 'separator'
						},
					     add_head:
						{
						     title: lang.add_head,
						     func: 'tableAddHead'
						},
					     delete_head:
						{
						     title: lang.delete_head,
						     func: 'tableDeleteHead'
						},
					     separator_drop3:
						{
						     name: 'separator'
						},
					     delete_column:
						{
						     title: lang.delete_column,
						     func: 'tableDeleteColumn'
						},
					     delete_row:
						{
						     title: lang.delete_row,
						     func: 'tableDeleteRow'
						},
					     delete_table:
						{
						     title: lang.delete_table,
						     func: 'tableDeleteTable'
						}
					}
				},
                    link: {
                         title: lang.link,
                         func: 'show',
                         dropdown:
					{
					     link:
						{
						     title: lang.link_insert,
						     func: 'linkShow'
						},
					     unlink:
						{
						     title: lang.unlink,
						     exec: 'unlink'
						}
					}
                    },
                    fontcolor:
				{
				     title: lang.fontcolor,
				     func: 'show'
				},
                    backcolor:
				{
				     title: lang.backcolor,
				     func: 'show'
				},
                    alignment:
				{
				     title: lang.alignment,
				     func: 'show',
				     dropdown:
					{
					     alignleft:
						{
						     title: lang.align_left,
						     func: 'alignmentLeft'
						},
					     aligncenter:
						{
						     title: lang.align_center,
						     func: 'alignmentCenter'
						},
					     alignright:
						{
						     title: lang.align_right,
						     func: 'alignmentRight'
						},
					     justify:
						{
						     title: lang.align_justify,
						     func: 'alignmentJustify'
						}
					}
				},
                    alignleft:
				{
				     title: lang.align_left,
				     func: 'alignmentLeft'
				},
                    aligncenter:
				{
				     title: lang.align_center,
				     func: 'alignmentCenter'
				},
                    alignright:
				{
				     title: lang.align_right,
				     func: 'alignmentRight'
				},
                    justify:
				{
				     title: lang.align_justify,
				     func: 'alignmentJustify'
				},
                    horizontalrule:
				{
				     exec: 'inserthorizontalrule',
				     title: lang.horizontalrule
				}

               }
          },

          // CALLBACKS
          callback: function (type, event, data) {
               var callback = this.opts[type + 'Callback'];
               if ($.isFunction(callback)) {
                    if (event === false) return callback.call(this, data);
                    else return callback.call(this, event, data);
               }
               else return data;
          },


          // DESTROY
          destroy: function () {
               clearInterval(this.autosaveInterval);

               $(window).off('.redactor');
               this.$source.off('redactor-textarea');
               this.$element.off('.redactor').removeData('redactor');

               var html = this.get();

               if (this.opts.textareamode) {
                    this.$box.after(this.$source);
                    this.$box.remove();
                    this.$source.val(html).show();
               }
               else {
                    var $elem = this.$editor;
                    if (this.opts.iframe) $elem = this.$element;

                    this.$box.after($elem);
                    this.$box.remove();

                    $elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show();
               }

               if (this.opts.air) {
                    $('.redactor_air').remove();
               }
          },

          // API GET
          getObject: function () {
               return $.extend({}, this);
          },
          getEditor: function () {
               return this.$editor;
          },
          getBox: function () {
               return this.$box;
          },
          getIframe: function () {
               return (this.opts.iframe) ? this.$frame : false;
          },
          getToolbar: function () {
               return this.$toolbar;
          },


          // CODE GET & SET
          get: function () {
               return this.$source.val();
          },
          getCodeIframe: function () {
               this.$editor.removeAttr('contenteditable').removeAttr('dir');
               var html = this.outerHtml(this.$frame.contents().children());
               this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });

               return html;
          },
          set: function (html, strip, placeholderRemove) {
               html = html.toString();

               if (this.opts.fullpage) this.setCodeIframe(html);
               else this.setEditor(html, strip);

               if (placeholderRemove !== false) this.placeholderRemove();
          },
          setEditor: function (html, strip) {
               if (strip !== false) {
                    html = this.cleanSavePreCode(html);
                    html = this.cleanStripTags(html);
                    html = this.cleanConvertProtected(html);
                    html = this.cleanConvertInlineTags(html);

                    if (this.opts.linebreaks === false) html = this.cleanConverters(html);
                    else html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
               }

               html = this.cleanEmpty(html);

               this.$editor.html(html);
               this.sync();
          },
          setCodeIframe: function (html) {
               var doc = this.iframePage();
               this.$frame[0].src = "about:blank";

               html = this.cleanConvertProtected(html);
               html = this.cleanConvertInlineTags(html);
               html = this.cleanRemoveSpaces(html);

               doc.open();
               doc.write(html);
               doc.close();

               // redefine editor for fullpage mode
               if (this.opts.fullpage) {
                    this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
               }

               this.sync();

          },
          setFullpageOnInit: function (html) {
               html = this.cleanSavePreCode(html, true);
               html = this.cleanConverters(html);
               html = this.cleanEmpty(html);

               // set code
               this.$editor.html(html);
               this.sync();
          },

          // SYNC
          sync: function () {
               var html = '';

               this.cleanUnverified();

               if (this.opts.fullpage) html = this.getCodeIframe();
               else html = this.$editor.html();

               html = this.syncClean(html);
               html = this.cleanRemoveSpaces(html);
               html = this.cleanRemoveEmptyTags(html);

               // fix second level up ul, ol
               html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');

               if ($.trim(html) === '<br>') html = '';

               if (html !== '' && this.opts.tidyHtml) html = this.cleanHtml(html);
               html = html.replace(/<br>/gi, '<br />');

               // before callback
               html = this.callback('syncBefore', false, html);

               this.$source.val(html);

               // onchange & after callback
               this.callback('syncAfter', false, html);

               if (this.start === false) {
                    this.callback('change', false, html);
               }

          },
          syncClean: function (html) {
               if (!this.opts.fullpage) html = this.cleanStripTags(html);

               html = $.trim(html);

               // removeplaceholder
               html = this.placeholderRemoveFromCode(html);

               // remove space
               html = html.replace(/&#x200b;/gi, '');
               html = html.replace(/&#8203;/gi, '');
               html = html.replace(/&nbsp;/gi, ' ');

               // link nofollow
               if (this.opts.linkNofollow) {
                    html = html.replace(/<a(.*?)rel="nofollow"(.*?)>/gi, '<a$1$2>');
                    html = html.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
               }

               // php code fix
               html = html.replace('<!--?php', '<?php');
               html = html.replace('?-->', '?>');

               html = html.replace(/ data-tagblock=""/gi, '');
               html = html.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');

               // remove image resize
               html = html.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/i, '$3<img$4>');
               html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/i, '');
               html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/i, '');

               // remove spans
               html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
               html = html.replace(/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
               html = html.replace(/<span(.*?)data-redactor-inlineMethods=""(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
               html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
               html = html.replace(/<span\s*?id="selection-marker(.*?)"(.*?)>([\w\W]*?)<\/span>/gi, '');
               html = html.replace(/<span\s*?>([\w\W]*?)<\/span>/gi, '$1');
               html = html.replace(/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
               html = html.replace(/<span(.*?)data-redactor-inlineMethods=""(.*?)>([\w\W]*?)<\/span>/gi, '<span$1$2>$3</span>');
               html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');

               // amp fix
               html = html.replace(/;amp;/gi, ';');


               html = this.cleanReConvertProtected(html);

               return html;
          },


          // BUILD
          buildStart: function () {
               // content
               this.content = '';

               // container
               this.$box = $('<div class="redactor_box" />');

               // textarea test
               if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;

               // mobile
               if (this.opts.mobile === false && this.isMobile()) {
                    this.buildMobile();
               }
               else {
                    // get the content at the start
                    this.buildContent();

                    if (this.opts.iframe) {
                         // build as iframe
                         this.opts.autoresize = false;
                         this.iframeStart();
                    }
                    else if (this.opts.textareamode) this.buildFromTextarea();
                    else this.buildFromElement();

                    // options and final setup
                    if (!this.opts.iframe) {
                         this.buildOptions();
                         this.buildAfter();
                    }
               }
          },
          buildMobile: function () {
               if (!this.opts.textareamode) {
                    this.$editor = this.$source;
                    this.$editor.hide();
                    this.$source = this.buildCodearea(this.$editor);
                    this.$source.val(this.content);
               }

               this.$box.insertAfter(this.$source).append(this.$source);
          },
          buildContent: function () {
               if (this.opts.textareamode) this.content = $.trim(this.$source.val());
               else this.content = $.trim(this.$source.html());
          },
          buildFromTextarea: function () {
               this.$editor = $('<div />');
               this.$box.insertAfter(this.$source).append(this.$editor).append(this.$source);

               // enable
               this.buildAddClasses(this.$editor);
               this.buildEnable();
          },
          buildFromElement: function () {
               this.$editor = this.$source;
               this.$source = this.buildCodearea(this.$editor);
               this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$source);

               // enable
               this.buildEnable();
          },
          buildCodearea: function ($source) {
               return $('<textarea />').attr('name', $source.attr('id')).css('height', this.sourceHeight);
          },
          buildAddClasses: function (el) {
               // append textarea classes to editable layer
               $.each(this.$source.get(0).className.split(/\s+/), function (i, s) {
                    el.addClass('redactor_' + s);
               });
          },
          buildEnable: function () {
               this.$editor.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts.direction });
               this.$source.attr('dir', this.opts.direction).hide();

               // set code
               this.set(this.content, true, false);
          },
          buildOptions: function () {
               var $source = this.$editor;
               if (this.opts.iframe) $source = this.$frame;

               // options
               if (this.opts.tabindex) $source.attr('tabindex', this.opts.tabindex);
               if (this.opts.minHeight) $source.css('min-height', this.opts.minHeight + 'px');
               if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
               if (!this.opts.autoresize) $source.css('height', this.sourceHeight);
          },
          buildAfter: function () {
               this.start = false;

               // load toolbar
               if (this.opts.toolbar) {
                    this.opts.toolbar = this.initToolbar(this.opts.curLang);
                    this.toolbarBuild();
               }

               // modal templates
               this.modalTemplatesInit();

               // plugins
               this.buildPlugins();

               // enter, tab, etc.
               this.buildBindKeyboard();

               // autosave
               if (this.opts.autosave) this.autosave();

               // observers
               setTimeout($.proxy(this.observeStart, this), 4);

               // FF fix
               if (this.browser('mozilla')) {
                    try {
                         this.document.execCommand('enableObjectResizing', false, false);
                         this.document.execCommand('enableInlineTableEditing', false, false);
                    } catch (e) { }
               }

               // focus
               if (this.opts.focus) setTimeout($.proxy(this.focus, this), 100);

               // code mode
               if (!this.opts.visual) {
                    setTimeout($.proxy(function () {
                         this.opts.visual = true;
                         this.toggle(false);

                    }, this), 200);
               }

               // init callback
               this.callback('init');
          },
          buildBindKeyboard: function () {
               if (this.opts.dragUpload) {
                    this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
               }

               this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
               this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
               this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));



               // textarea callback
               if ($.isFunction(this.opts.textareaKeydownCallback)) {
                    this.$source.on('keydown.redactor-textarea', $.proxy(this.opts.textareaKeydownCallback, this));
               }

               // focus callback
               if ($.isFunction(this.opts.focusCallback)) {
                    this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
               }

               // blur callback
               this.$editor.on('blur.redactor', $.proxy(function () {
                    this.selectall = false;
               }, this));
               if ($.isFunction(this.opts.blurCallback)) {
                    this.$editor.on('blur.redactor', $.proxy(this.opts.blurCallback, this));
               }

          },
          buildEventDrop: function (e) {
               e = e.originalEvent || e;

               if (window.FormData === undefined) return true;

               var length = e.dataTransfer.files.length;
               if (length == 0) return true;

               e.preventDefault();

               var file = e.dataTransfer.files[0];

               if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1) {
                    return true;
               }

               this.bufferSet();

               var progress = $('<div id="redactor-progress-drag" class="redactor-progress redactor-progress-striped"><div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div></div>');
               $(document.body).append(progress);

               this.dragUploadAjax(this.opts.imageUpload, file, true, progress, e);

          },
          buildEventPaste: function (e) {
               var oldsafari = false;
               if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1) {
                    var arr = this.browser('version').split('.');
                    if (arr[0] < 536) oldsafari = true;
               }

               if (oldsafari) return true;

               // paste except opera (not webkit)
               if (this.browser('opera')) return true;

               // clipboard upload
               if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;

               if (this.opts.cleanup) {
                    rtePaste = true;

                    this.selectionSave();

                    if (!this.selectall) {
                         if (this.opts.autoresize === true) {
                              this.$editor.height(this.$editor.height());
                              this.saveScroll = this.document.body.scrollTop;
                         }
                         else {
                              this.saveScroll = this.$editor.scrollTop();
                         }
                    }

                    var frag = this.extractContent();

                    setTimeout($.proxy(function () {
                         var pastedFrag = this.extractContent();
                         this.$editor.append(frag);

                         this.selectionRestore();

                         var html = this.getFragmentHtml(pastedFrag);
                         this.pasteClean(html);

                         if (this.opts.autoresize === true) this.$editor.css('height', 'auto');

                    }, this), 1);
               }
          },
          buildEventClipboardUpload: function (e) {
               var event = e.originalEvent || e;
               this.clipboardFilePaste = false;

               if (typeof (event.clipboardData) === 'undefined') return false;
               if (event.clipboardData.items) {
                    var file = event.clipboardData.items[0].getAsFile();
                    if (file !== null) {
                         this.bufferSet();
                         this.clipboardFilePaste = true;

                         var reader = new FileReader();
                         reader.onload = $.proxy(this.pasteClipboardUpload, this);
                         reader.readAsDataURL(file);

                         return true;
                    }
               }

               return false;

          },
          buildEventKeydown: function (e) {
               if (rtePaste) return false;

               var key = e.which;
               var ctrl = e.ctrlKey || e.metaKey;
               var parent = this.getParent();
               var current = this.getCurrent();
               var block = this.getBlock();
               var pre = false;

               this.callback('keydown', e);

               this.imageResizeHide(false);

               // pre & down
               if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE')) {
                    pre = true;
                    if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
               }

               // down
               if (key === this.keyCode.DOWN) {
                    if (parent && $(parent).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
                    if (current && $(current).get(0).tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
               }

               // shortcuts setup
               if (ctrl && !e.shiftKey) this.shortcuts(e, key);

               // buffer setup
               if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
               {
                    e.preventDefault();
                    if (this.opts.buffer.length) this.bufferUndo();
                    else this.document.execCommand('undo', false, false);
                    return;
               }
               // undo
               else if (ctrl && key === 90 && e.shiftKey && !e.altKey) {
                    e.preventDefault();
                    if (this.opts.rebuffer.length != 0) this.bufferRedo();
                    else this.document.execCommand('redo', false, false);
                    return;
               }

               // select all
               if (ctrl && key === 65) this.selectall = true;
               else if (key != this.keyCode.LEFT_WIN && !ctrl) this.selectall = false;

               // enter
               if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
                    // In ie, opera in the tables are created paragraphs, fix it.
                    if (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH')) {
                         e.preventDefault();
                         this.bufferSet();
                         this.insertNode(document.createElement('br'));
                         this.callback('enter', e);
                         return false;
                    }

                    // pre
                    if (pre === true) {
                         e.preventDefault();
                         this.bufferSet();
                         var html = $(current).parent().text();
                         this.insertNode(document.createTextNode('\n'));
                         if (html.search(/\s$/) == -1) {
                              this.insertNode(document.createTextNode('\n'));
                         }

                         this.sync();
                         this.callback('enter', e);
                         return false;
                    }
                    else {
                         if (!this.opts.linebreaks) {
                              // replace div to p
                              if (block && this.opts.rBlockTest.test(block.tagName)) {
                                   // hit enter
                                   this.bufferSet();

                                   setTimeout($.proxy(function () {
                                        var blockElem = this.getBlock();
                                        if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor')) {
                                             var node = $('<p>' + this.opts.invisibleSpace + '</p>');
                                             $(blockElem).replaceWith(node);
                                             this.selectionStart(node);
                                        }

                                   }, this), 1);
                              }
                              else if (block === false) {
                                   // hit enter
                                   this.bufferSet();

                                   var node = $('<p>' + this.opts.invisibleSpace + '</p>');
                                   this.insertNode(node[0]);
                                   this.selectionStart(node);
                                   this.callback('enter', e);
                                   return false;
                              }

                         }

                         if (this.opts.linebreaks) {
                              // replace div to br
                              if (block && this.opts.rBlockTest.test(block.tagName)) {
                                   // hit enter
                                   this.bufferSet();

                                   setTimeout($.proxy(function () {
                                        var blockElem = this.getBlock();
                                        if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor')) {
                                             this.replaceLineBreak(blockElem);
                                        }

                                   }, this), 1);
                              }
                              else {
                                   return this.buildEventKeydownInsertLineBreak(e);
                              }
                         }

                         // blockquote, figcaption
                         if (block.tagName == 'BLOCKQUOTE' || block.tagName == 'FIGCAPTION') {
                              return this.buildEventKeydownInsertLineBreak(e);
                         }

                    }

                    this.callback('enter', e);
               }
               else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
               {
                    this.bufferSet();

                    e.preventDefault();
                    this.insertLineBreak();
               }

               // tab
               if (key === this.keyCode.TAB && this.opts.shortcuts) {
                    if (!this.opts.tabFocus) return true;
                    if (this.isEmpty(this.get())) return true;

                    e.preventDefault();

                    if (pre === true && !e.shiftKey) {
                         this.bufferSet();
                         this.insertNode(document.createTextNode('\t'));
                         this.sync();
                         return false;

                    }
                    else if (this.opts.tabSpaces !== false) {
                         this.bufferSet();
                         this.insertNode(document.createTextNode(Array(this.opts.tabSpaces + 1).join('\u00a0')));
                         this.sync();
                         return false;
                    }
                    else {
                         if (!e.shiftKey) this.indentingIndent();
                         else this.indentingOutdent();
                    }

                    return false;
               }

               // delete zero-width space before the removing
               if (key === this.keyCode.BACKSPACE) {
                    if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName)) {
                         var node;
                         if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
                         else node = $('<br>' + this.opts.invisibleSpace);

                         $(current).replaceWith(node);
                         this.selectionStart(node);
                    }

                    if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null) {
                         var value = $.trim(current.nodeValue.replace(/[^\u0000-\u1C7F]/g, ''));
                         if (current.remove && current.nodeType === 3 && current.nodeValue.charCodeAt(0) == 8203 && value == '') {
                              current.remove();
                         }
                    }
               }
          },
          buildEventKeydownInsertLineBreak: function (e) {
               this.bufferSet();
               e.preventDefault();
               this.insertLineBreak();
               this.callback('enter', e);
               return;
          },
          buildEventKeyup: function (e) {
               if (rtePaste) return false;

               var key = e.which;
               var parent = this.getParent();
               var current = this.getCurrent();

               // replace to p before / after the table or body
               if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY')) {
                    var node = $('<p>').append($(current).clone());
                    $(current).replaceWith(node);
                    var next = $(node).next();
                    if (typeof (next[0]) !== 'undefined' && next[0].tagName == 'BR') {
                         next.remove();
                    }
                    this.selectionEnd(node);
               }

               // convert links
               if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER) {
                    this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks);

                    setTimeout($.proxy(function () {
                         if (this.opts.convertImageLinks) this.observeImages();
                         if (this.opts.observeLinks) this.observeLinks();
                    }, this), 5);
               }

               // if empty
               if (this.opts.linebreaks === false && (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)) {
                    return this.formatEmpty(e);
               }

               this.callback('keyup', e);
               this.sync();
          },
          buildPlugins: function () {
               if (!this.opts.plugins) return;

               $.each(this.opts.plugins, $.proxy(function (i, s) {
                    if (RedactorPlugins[s]) {
                         $.extend(this, RedactorPlugins[s]);
                         if ($.isFunction(RedactorPlugins[s].init)) this.init();
                    }

               }, this));
          },

          // IFRAME
          iframeStart: function () {
               this.iframeCreate();

               if (this.opts.textareamode) this.iframeAppend(this.$source);
               else {
                    this.$sourceOld = this.$source.hide();
                    this.$source = this.buildCodearea(this.$sourceOld);
                    this.iframeAppend(this.$sourceOld);
               }
          },
          iframeAppend: function (el) {
               this.$source.attr('dir', this.opts.direction).hide();
               this.$box.insertAfter(el).append(this.$frame).append(this.$source);
          },
          iframeCreate: function () {
               this.$frame = $('<iframe style="width: 100%;" frameborder="0" />').one('load', $.proxy(function () {
                    if (this.opts.fullpage) {
                         this.iframePage();

                         if (this.content === '') this.content = this.opts.invisibleSpace;

                         this.$frame.contents()[0].write(this.content);
                         this.$frame.contents()[0].close();

                         var timer = setInterval($.proxy(function () {
                              if (this.$frame.contents().find('body').html()) {
                                   clearInterval(timer);
                                   this.iframeLoad();
                              }

                         }, this), 0);
                    }
                    else this.iframeLoad();

               }, this));
          },
          iframeDoc: function () {
               return this.$frame[0].contentWindow.document;
          },
          iframePage: function () {
               var doc = this.iframeDoc();
               if (doc.documentElement) doc.removeChild(doc.documentElement);

               return doc;
          },
          iframeAddCss: function (css) {
               css = css || this.opts.css;

               if (this.isString(css)) {
                    this.$frame.contents().find('head').append('<link rel="stylesheet" href="' + css + '" />');
               }

               if ($.isArray(css)) {
                    $.each(css, $.proxy(function (i, url) {
                         this.iframeAddCss(url);

                    }, this));
               }
          },
          iframeLoad: function () {
               this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });

               // set document & window
               if (this.$editor[0]) {
                    this.document = this.$editor[0].ownerDocument;
                    this.window = this.document.defaultView || window;
               }

               // iframe css
               this.iframeAddCss();

               if (this.opts.fullpage) this.setFullpageOnInit(this.$editor.html());
               else this.set(this.content, true, false);

               this.buildOptions();
               this.buildAfter();
          },

          // PLACEHOLDER
          placeholderStart: function (html) {
               if (this.isEmpty(html)) {
                    if (this.$element.attr('placeholder')) this.opts.placeholder = this.$element.attr('placeholder');
                    if (this.opts.placeholder === '') this.opts.placeholder = false;

                    if (this.opts.placeholder !== false) {
                         this.opts.focus = false;
                         this.$editor.one('focus.redactor_placeholder', $.proxy(this.placeholderFocus, this));

                         return $('<span class="redactor_placeholder" data-redactor="verified">').attr('contenteditable', false).text(this.opts.placeholder);
                    }
               }

               return false;
          },
          placeholderFocus: function () {
               this.$editor.find('span.redactor_placeholder').remove();

               var html = '';
               if (this.opts.linebreaks === false) html = this.opts.emptyHtml;

               this.$editor.off('focus.redactor_placeholder');
               this.$editor.html(html);

               if (this.opts.linebreaks === false) {
                    // place the cursor inside emptyHtml
                    this.selectionStart(this.$editor.children()[0]);
               }

               this.sync();
          },
          placeholderRemove: function () {
               this.opts.placeholder = false;
               this.$editor.find('span.redactor_placeholder').remove();
               this.$editor.off('focus.redactor_placeholder');
          },
          placeholderRemoveFromCode: function (html) {
               return html.replace(/<span class="redactor_placeholder"(.*?)>(.*?)<\/span>/i, '');
          },

          // SHORTCUTS
          shortcuts: function (e, key) {

               if (!this.opts.shortcuts) return;

               if (!e.altKey) {
                    if (key === 77) this.shortcutsLoad(e, 'removeFormat'); // Ctrl + m
                    else if (key === 66) this.shortcutsLoad(e, 'bold'); // Ctrl + b
                    else if (key === 73) this.shortcutsLoad(e, 'italic'); // Ctrl + i

                    else if (key === 74) this.shortcutsLoad(e, 'insertunorderedlist'); // Ctrl + j
                    else if (key === 75) this.shortcutsLoad(e, 'insertorderedlist'); // Ctrl + k

                    else if (key === 72) this.shortcutsLoad(e, 'superscript'); // Ctrl + h
                    else if (key === 76) this.shortcutsLoad(e, 'subscript'); // Ctrl + l
               }
               else {
                    if (key === 48) this.shortcutsLoadFormat(e, 'p'); // ctrl + alt + 0
                    else if (key === 49) this.shortcutsLoadFormat(e, 'h1'); // ctrl + alt + 1
                    else if (key === 50) this.shortcutsLoadFormat(e, 'h2'); // ctrl + alt + 2
                    else if (key === 51) this.shortcutsLoadFormat(e, 'h3'); // ctrl + alt + 3
                    else if (key === 52) this.shortcutsLoadFormat(e, 'h4'); // ctrl + alt + 4
                    else if (key === 53) this.shortcutsLoadFormat(e, 'h5'); // ctrl + alt + 5
                    else if (key === 54) this.shortcutsLoadFormat(e, 'h6'); // ctrl + alt + 6

               }

          },
          shortcutsLoad: function (e, cmd) {
               e.preventDefault();
               this.execCommand(cmd, false);
          },
          shortcutsLoadFormat: function (e, cmd) {
               e.preventDefault();
               this.formatBlocks(cmd);
          },

          // FOCUS
          focus: function () {
               if (!this.browser('opera')) this.window.setTimeout($.proxy(this.focusSet, this, true), 1);
               else this.$editor.focus();
          },
          focusEnd: function () {
               this.focusSet();
          },
          focusSet: function (collapse) {
               this.$editor.focus();

               var range = this.getRange();
               range.selectNodeContents(this.$editor[0]);

               // collapse - controls the position of focus: the beginning (true), at the end (false).
               range.collapse(collapse || false);

               var sel = this.getSelection();
               sel.removeAllRanges();
               sel.addRange(range);
          },


          // TOGGLE
          toggle: function (direct) {
               var html;
               if (this.opts.visual) {
                    if (direct !== false) this.selectionSave();

                    var height = null;
                    if (this.opts.iframe) {
                         height = this.$frame.height();
                         if (this.opts.fullpage) this.$editor.removeAttr('contenteditable');
                         this.$frame.hide();
                    }
                    else {
                         height = this.$editor.innerHeight();
                         this.$editor.hide();
                    }

                    html = this.$source.val();
                    this.modified = html;

                    this.$source.height(height).show().focus();

                    // textarea indenting
                    this.$source.on('keydown.redactor-textarea-indenting', function (e) {
                         if (e.keyCode === 9) {
                              var $el = $(this);
                              var start = $el.get(0).selectionStart;
                              $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
                              $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
                              return false;
                         }
                    });

                    this.buttonInactiveVisual();
                    this.buttonActive('html');
                    this.opts.visual = false;

               }
               else {
                    html = this.$source.hide().val();

                    if (typeof this.modified !== 'undefined') {
                         this.modified = this.cleanRemoveSpaces(this.modified, false) !== this.cleanRemoveSpaces(html, false);
                    }

                    if (this.modified) {
                         // don't remove the iframe even if cleared all.
                         if (this.opts.fullpage && html === '') this.setFullpageOnInit(html);
                         else {
                              this.set(html);
                              if (this.opts.fullpage) this.buildBindKeyboard();
                         }
                    }

                    if (this.opts.iframe) this.$frame.show();
                    else this.$editor.show();

                    if (this.opts.fullpage) this.$editor.attr('contenteditable', true);

                    this.$source.off('keydown.redactor-textarea-indenting');

                    this.$editor.focus();
                    this.selectionRestore();

                    this.observeStart();
                    this.buttonActiveVisual();
                    this.buttonInactive('html');
                    this.opts.visual = true;
               }
          },


          // AUTOSAVE
          autosave: function () {
               var savedHtml = false;
               this.autosaveInterval = setInterval($.proxy(function () {
                    var html = this.get();
                    if (savedHtml !== html) {
                         $.ajax({
                              url: this.opts.autosave,
                              type: 'post',
                              data: this.$source.attr('name') + '=' + escape(encodeURIComponent(html)),
                              success: $.proxy(function (data) {
                                   this.callback('autosave', false, data);
                                   savedHtml = html;

                              }, this)
                         });
                    }
               }, this), this.opts.autosaveInterval * 1000);
          },

          // TOOLBAR
          toolbarBuild: function () {
               // extend buttons
               if (this.opts.air) {
                    this.opts.buttons = this.opts.airButtons;
               }
               else {
                    if (!this.opts.buttonSource) {
                         var index = this.opts.buttons.indexOf('html'), next = this.opts.buttons[index + 1];
                         this.opts.buttons.splice(index, 1);
                         if (next === '|') this.opts.buttons.splice(index, 1);
                    }
               }

               $.extend(this.opts.toolbar, this.opts.buttonsCustom);
               $.each(this.opts.buttonsAdd, $.proxy(function (i, s) {
                    this.opts.buttons.push(s);

               }, this));

               // formatting tags
               if (this.opts.toolbar) {
                    $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s) {
                         if ($.inArray(i, this.opts.formattingTags) == '-1') delete this.opts.toolbar.formatting.dropdown[i];

                    }, this));
               }

               // if no buttons don't create a toolbar
               if (this.opts.buttons.length === 0) return false;

               // air enable
               this.airEnable();

               // toolbar build
               this.$toolbar = $('<ul>').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid);

               if (this.opts.air) {
                    // air box
                    this.$air = $('<div class="redactor_air">').attr('id', 'redactor_air_' + this.uuid).hide();
                    this.$air.append(this.$toolbar);
                    $('body').append(this.$air);
               }
               else {
                    if (this.opts.toolbarExternal) $(this.opts.toolbarExternal).html(this.$toolbar);
                    else this.$box.prepend(this.$toolbar);
               }

               $.each(this.opts.buttons, $.proxy(function (i, btnName) {
                    // separator
                    if (btnName === '|') this.$toolbar.append($(this.opts.buttonSeparator));
                    else if (this.opts.toolbar[btnName]) {
                         var btnObject = this.opts.toolbar[btnName];
                         if (this.opts.fileUpload === false && btnName === 'file') return true;
                         this.$toolbar.append($('<li>').append(this.buttonBuild(btnName, btnObject)));
                    }

               }, this));

               this.$toolbar.find('a').attr('tabindex', '-1');

               // fixed
               if (this.opts.toolbarFixed) {
                    this.toolbarObserveScroll();
                    $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
               }

               // buttons response
               if (this.opts.activeButtons) {
                    var buttonActiveObserver = $.proxy(this.buttonActiveObserver, this);
                    this.$editor.on('mouseup.redactor keyup.redactor', buttonActiveObserver);
               }
          },
          toolbarObserveScroll: function () {
               var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
               var boxTop = this.$box.offset().top;
               var left = 0;

               var end = boxTop + this.$box.height() + 40;

               if (scrollTop > boxTop) {
                    var width = '100%';
                    if (this.opts.toolbarFixedBox) {
                         left = this.$box.offset().left;
                         width = this.$box.innerWidth();
                         this.$toolbar.addClass('toolbar_fixed_box');
                    }

                    this.toolbarFixed = true;
                    this.$toolbar.css({
                         position: 'fixed',
                         width: width,
                         zIndex: 1005,
                         top: this.opts.toolbarFixedTopOffset + 'px',
                         left: left
                    });

                    if (scrollTop < end) this.$toolbar.css('visibility', 'visible');
                    else this.$toolbar.css('visibility', 'hidden');
               }
               else {
                    this.toolbarFixed = false;
                    this.$toolbar.css({
                         position: 'relative',
                         width: 'auto',
                         top: 0,
                         left: left
                    });

                    if (this.opts.toolbarFixedBox) this.$toolbar.removeClass('toolbar_fixed_box');
               }
          },

          // AIR
          airEnable: function () {
               if (!this.opts.air) return;

               this.$editor.on('mouseup.redactor keyup.redactor', this, $.proxy(function (e) {
                    var text = this.getSelectionText();

                    if (e.type === 'mouseup' && text != '') this.airShow(e);

                    if (e.type === 'keyup' && e.shiftKey && text != '') {
                         var $focusElem = $(this.getElement(this.getSelection().focusNode)), offset = $focusElem.offset();
                         offset.height = $focusElem.height();
                         this.airShow(offset, true);
                    }

               }, this));
          },
          airShow: function (e, keyboard) {
               if (!this.opts.air) return;

               var left, top;
               $('.redactor_air').hide();

               if (keyboard) {
                    left = e.left;
                    top = e.top + e.height + 14;

                    if (this.opts.iframe) {
                         top += this.$box.position().top - $(this.document).scrollTop();
                         left += this.$box.position().left;
                    }
               }
               else {
                    var width = this.$air.innerWidth();

                    left = e.clientX;
                    if ($(this.document).width() < (left + width)) left -= width;

                    top = e.clientY + 14;
                    if (this.opts.iframe) {
                         top += this.$box.position().top;
                         left += this.$box.position().left;
                    }
                    else top += $(this.document).scrollTop();
               }

               this.$air.css({
                    left: left + 'px',
                    top: top + 'px'
               }).show();

               this.airBindHide();
          },
          airBindHide: function () {
               if (!this.opts.air) return;

               var hideHandler = $.proxy(function (doc) {
                    $(doc).on('mousedown.redactor', $.proxy(function (e) {
                         if ($(e.target).closest(this.$toolbar).length === 0) {
                              this.$air.fadeOut(100);
                              this.selectionRemove();
                              $(doc).off(e);
                         }

                    }, this)).on('keydown.redactor', $.proxy(function (e) {
                         if (e.which === this.keyCode.ESC) {
                              this.getSelection().collapseToStart();
                         }

                         this.$air.fadeOut(100);
                         $(doc).off(e);

                    }, this));
               }, this);

               // Hide the toolbar at events in all documents (iframe)
               hideHandler(document);
               if (this.opts.iframe) hideHandler(this.document);
          },
          airBindMousemoveHide: function () {
               if (!this.opts.air) return;

               var hideHandler = $.proxy(function (doc) {
                    $(doc).on('mousemove.redactor', $.proxy(function (e) {
                         if ($(e.target).closest(this.$toolbar).length === 0) {
                              this.$air.fadeOut(100);
                              $(doc).off(e);
                         }

                    }, this));
               }, this);

               // Hide the toolbar at events in all documents (iframe)
               hideHandler(document);
               if (this.opts.iframe) hideHandler(this.document);
          },

          // DROPDOWNS
          dropdownBuild: function ($dropdown, dropdownObject) {
               $.each(dropdownObject, $.proxy(function (btnName, btnObject) {
                    if (!btnObject.className) btnObject.className = '';

                    var $item;
                    if (btnObject.name === 'separator') $item = $('<a class="redactor_separator_drop">');
                    else {
                         $item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
                         $item.on('click', $.proxy(function (e) {
                              if (e.preventDefault) e.preventDefault();
                              if (this.browser('msie')) e.returnValue = false;

                              if (btnObject.callback) btnObject.callback.call(this, btnName, $item, btnObject, e);
                              if (btnObject.exec) this.execCommand(btnObject.exec, btnName);
                              if (btnObject.func) this[btnObject.func](btnName);

                              this.buttonActiveObserver();
                              if (this.opts.air) this.$air.fadeOut(100);

                         }, this));
                    }

                    $dropdown.append($item);

               }, this));
          },
          dropdownShow: function (e, key) {
               if (!this.opts.visual) {
                    e.preventDefault();
                    return false;
               }

               var $dropdown = this.$toolbar.find('.redactor_dropdown_box_' + key);
               var $button = this.buttonGet(key);

               if ($button.hasClass('dropact')) this.dropdownHideAll();
               else {
                    this.dropdownHideAll();

                    this.buttonActive(key);
                    $button.addClass('dropact');

                    var keyPosition = $button.position();
                    if (this.toolbarFixed) {
                         keyPosition = $button.offset();
                    }

                    // fix right placement
                    var dropdownWidth = $dropdown.width();
                    if ((keyPosition.left + dropdownWidth) > $(document).width()) {
                         keyPosition.left -= dropdownWidth;
                    }

                    var left = keyPosition.left + 'px';
                    var btnHeight = 29;

                    var position = 'absolute';
                    var top = btnHeight + 'px';

                    if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed';
                    else if (!this.opts.air) top = keyPosition.top + btnHeight + 'px';

                    $dropdown.css({ position: position, left: left, top: top }).show();
               }


               var hdlHideDropDown = $.proxy(function (e) {
                    this.dropdownHide(e, $dropdown);

               }, this);

               $(document).one('click', hdlHideDropDown);
               this.$editor.one('click', hdlHideDropDown);

               e.stopPropagation();
          },
          dropdownHideAll: function () {
               this.$toolbar.find('a.dropact').removeClass('redactor_act').removeClass('dropact');
               $('.redactor_dropdown').hide();
          },
          dropdownHide: function (e, $dropdown) {
               if (!$(e.target).hasClass('dropact')) {
                    $dropdown.removeClass('dropact');
                    this.dropdownHideAll();
               }
          },

          // BUTTONS
          buttonBuild: function (btnName, btnObject) {
               var $button = $('<a href="javascript:;" title="' + btnObject.title + '" class="redactor_btn redactor_btn_' + btnName + '"></a>');

               $button.on('click', $.proxy(function (e) {
                    if (e.preventDefault) e.preventDefault();
                    if (this.browser('msie')) e.returnValue = false;

                    if ($button.hasClass('redactor_button_disabled')) return false;

                    if (this.isFocused() === false && !btnObject.exec) {
                         this.$editor.focus();
                    }

                    if (btnObject.exec) {
                         this.$editor.focus();
                         this.execCommand(btnObject.exec, btnName);
                         this.airBindMousemoveHide();

                    }
                    else if (btnObject.func && btnObject.func !== 'show') {
                         this[btnObject.func](btnName);
                         this.airBindMousemoveHide();

                    }
                    else if (btnObject.callback) {
                         btnObject.callback.call(this, btnName, $button, btnObject, e);
                         this.airBindMousemoveHide();

                    }
                    else if (btnObject.dropdown) {
                         this.dropdownShow(e, btnName);
                    }

                    this.buttonActiveObserver(false, btnName);

               }, this));

               // dropdown
               if (btnObject.dropdown) {
                    var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">');
                    $dropdown.appendTo(this.$toolbar);
                    this.dropdownBuild($dropdown, btnObject.dropdown);
               }

               return $button;
          },
          buttonGet: function (key) {
               if (!this.opts.toolbar) return false;
               return $(this.$toolbar.find('a.redactor_btn_' + key));
          },
          buttonActiveToggle: function (key) {
               var btn = this.buttonGet(key);

               if (btn.hasClass('redactor_act')) btn.removeClass('redactor_act');
               else btn.addClass('redactor_act');
          },
          buttonActive: function (key) {
               this.buttonGet(key).addClass('redactor_act');
          },
          buttonInactive: function (key) {
               this.buttonGet(key).removeClass('redactor_act');
          },
          buttonInactiveAll: function (btnName) {
               $.each(this.opts.toolbar, $.proxy(function (k) {
                    if (k != btnName) this.buttonInactive(k);

               }, this));
          },
          buttonActiveVisual: function () {
               this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').removeClass('redactor_button_disabled');
          },
          buttonInactiveVisual: function () {
               this.$toolbar.find('a.redactor_btn').not('a.redactor_btn_html').addClass('redactor_button_disabled');
          },
          buttonChangeIcon: function (key, classname) {
               this.buttonGet(key).addClass('redactor_btn_' + classname);
          },
          buttonRemoveIcon: function (key, classname) {
               this.buttonGet(key).removeClass('redactor_btn_' + classname);
          },
          buttonAddSeparator: function () {
               this.$toolbar.append($(this.opts.buttonSeparator));
          },
          buttonAddSeparatorAfter: function (key) {
               this.buttonGet(key).parent().after($(this.opts.buttonSeparator));
          },
          buttonAddSeparatorBefore: function (key) {
               this.buttonGet(key).parent().before($(this.opts.buttonSeparator));
          },
          buttonRemoveSeparatorAfter: function (key) {
               this.buttonGet(key).parent().next().remove();
          },
          buttonRemoveSeparatorBefore: function (key) {
               this.buttonGet(key).parent().prev().remove();
          },
          buttonSetRight: function (key) {
               if (!this.opts.toolbar) return;
               this.buttonGet(key).parent().addClass('redactor_btn_right');
          },
          buttonSetLeft: function (key) {
               if (!this.opts.toolbar) return;
               this.buttonGet(key).parent().removeClass('redactor_btn_right');
          },
          buttonAdd: function (key, title, callback, dropdown) {
               if (!this.opts.toolbar) return;
               var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
               this.$toolbar.append($('<li>').append(btn));
          },
          buttonAddFirst: function (key, title, callback, dropdown) {
               if (!this.opts.toolbar) return;
               var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
               this.$toolbar.prepend($('<li>').append(btn));
          },
          buttonAddAfter: function (afterkey, key, title, callback, dropdown) {
               if (!this.opts.toolbar) return;
               var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
               var $btn = this.buttonGet(afterkey);

               if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
               else this.$toolbar.append($('<li>').append(btn));
          },
          buttonAddBefore: function (beforekey, key, title, callback, dropdown) {
               if (!this.opts.toolbar) return;
               var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown });
               var $btn = this.buttonGet(beforekey);

               if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
               else this.$toolbar.append($('<li>').append(btn));
          },
          buttonRemove: function (key, separator) {
               var $btn = this.buttonGet(key);
               if (separator) $btn.parent().next().remove();
               $btn.parent().removeClass('redactor_btn_right');
               $btn.remove();
          },
          buttonActiveObserver: function (e, btnName) {
               var parent = this.getParent();
               this.buttonInactiveAll(btnName);

               if (e === false && btnName !== 'html') {
                    if ($.inArray(btnName, this.opts.activeButtons) != -1) {
                         this.buttonActiveToggle(btnName);
                    }
                    return;
               }

               if (parent && parent.tagName === 'A') this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_edit);
               else this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_insert);

               if (this.opts.activeButtonsAdd) {
                    $.each(this.opts.activeButtonsAdd, $.proxy(function (i, s) {
                         this.opts.activeButtons.push(s);

                    }, this));

                    $.extend(this.opts.activeButtonsStates, this.opts.activeButtonsAdd);
               }

               $.each(this.opts.activeButtonsStates, $.proxy(function (key, value) {
                    if ($(parent).closest(key, this.$editor.get()[0]).length != 0) {
                         this.buttonActive(value);
                    }

               }, this));

               var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
               if ($parent.length) {
                    var align = $parent.css('text-align');

                    switch (align) {
                         case 'right':
                              this.buttonActive('alignright');
                              break;
                         case 'center':
                              this.buttonActive('aligncenter');
                              break;
                         case 'justify':
                              this.buttonActive('justify');
                              break;
                         default:
                              this.buttonActive('alignleft');
                              break;
                    }
               }
          },

          // EXEC
          exec: function (cmd, param, sync) {
               if (cmd === 'formatblock' && this.browser('msie')) param = '<' + param + '>';

               if (cmd === 'inserthtml' && this.browser('msie')) {
                    this.$editor.focus();
                    this.document.selection.createRange().pasteHTML(param);
               }
               else {
                    this.document.execCommand(cmd, false, param);
               }

               if (sync !== false) this.sync();
               this.callback('execCommand', cmd, param);
          },
          execCommand: function (cmd, param, sync) {
               if (!this.opts.visual) {
                    this.$source.focus();
                    return false;
               }

               if (cmd === 'inserthtml') {
                    this.insertHtml(param, sync);
                    this.callback('execCommand', cmd, param);
                    return;
               }

               // stop formatting pre
               if (this.currentOrParentIs('PRE') && !this.opts.formattingPre) {
                    return false;
               }

               if (cmd === 'insertunorderedlist' || cmd === 'insertorderedlist') {
                    this.bufferSet();

                    var parent = this.getParent();
                    var $list = $(parent).closest('ol, ul');
                    var remove = false;

                    if ($list.length) {
                         remove = true;
                         var listTag = $list[0].tagName;
                         if ((cmd === 'insertunorderedlist' && listTag === 'OL')
				 	|| (cmd === 'insertorderedlist' && listTag === 'UL')) {
                              remove = false;
                         }
                    }

                    this.selectionSave();

                    // remove lists
                    if (remove) {
                         var nodes = this.getNodes();
                         var elems = this.getBlocks(nodes);

                         if (typeof nodes[0] != 'undefined' && nodes.length > 1 && nodes[0].nodeType == 3) {
                              // fix the adding the first li to the array
                              elems.unshift(this.getBlock());
                         }

                         var data = '', replaced = '';
                         $.each(elems, $.proxy(function (i, s) {
                              if (s.tagName == 'LI') {
                                   var $s = $(s);
                                   var cloned = $s.clone();
                                   cloned.find('ul', 'ol').remove();

                                   if (this.opts.linebreaks === false) data += this.outerHtml($('<p>').append(cloned.contents()));
                                   else data += cloned.html() + '<br>';

                                   if (i == 0) {
                                        $s.addClass('redactor-replaced').empty();
                                        replaced = this.outerHtml($s);
                                   }
                                   else $s.remove();
                              }

                         }, this));

                         html = this.$editor.html().replace(replaced, '</' + listTag + '>' + data + '<' + listTag + '>');

                         this.$editor.html(html);
                         this.$editor.find(listTag + ':empty').remove();

                    }

                    // insert lists
                    else {
                         this.document.execCommand(cmd);

                         var parent = this.getParent();
                         var $list = $(parent).closest('ol, ul');

                         if ($list.length) {
                              if ((this.browser('msie') || this.browser('mozilla')) && parent.tagName !== 'LI') {
                                   $(parent).replaceWith($(parent).html());
                              }

                              var $listParent = $list.parent();
                              if (this.isParentRedactor($listParent) && this.nodeTestBlocks($listParent[0])) {
                                   $listParent.replaceWith($listParent.contents());
                              }
                         }

                         if (this.browser('mozilla')) this.$editor.focus();

                    }

                    this.selectionRestore();

                    this.sync();
                    this.callback('execCommand', cmd, param);
                    return;
               }

               if (cmd === 'unlink') {
                    this.bufferSet();

                    var link = this.currentOrParentIs('A');
                    if (link) {
                         $(link).replaceWith($(link).text());

                         this.sync();
                         this.callback('execCommand', cmd, param);
                         return;
                    }
               }

               this.exec(cmd, param, sync);

               if (cmd === 'inserthorizontalrule') {
                    this.$editor.find('hr').removeAttr('id');
               }
          },

          // INDENTING
          indentingIndent: function () {
               this.indentingStart('indent');
          },
          indentingOutdent: function () {
               this.indentingStart('outdent');
          },
          indentingStart: function (cmd) {
               this.bufferSet();

               if (cmd === 'indent') {
                    var block = this.getBlock();

                    this.selectionSave();


                    if (block && block.tagName == 'LI') {
                         // li
                         var parent = this.getParent();

                         var $list = $(parent).closest('ol, ul');
                         var listTag = $list[0].tagName;

                         var elems = this.getBlocks();

                         $.each(elems, function (i, s) {
                              if (s.tagName == 'LI') {
                                   var $prev = $(s).prev();
                                   if ($prev.size() != 0 && $prev[0].tagName == 'LI') {
                                        var $childList = $prev.children('ul, ol');
                                        if ($childList.size() == 0) {
                                             $prev.append($('<' + listTag + '>').append(s));
                                        }
                                        else $childList.append(s);
                                   }
                              }
                         });
                    }
                    // linebreaks
                    else if (block === false && this.opts.linebreaks === true) {
                         this.exec('formatBlock', 'blockquote');
                         var newblock = this.getBlock();
                         var block = $('<div data-tagblock="">').html($(newblock).html());
                         $(newblock).replaceWith(block);

                         var left = this.normalize($(block).css('margin-left')) + this.opts.indentValue;
                         $(block).css('margin-left', left + 'px');
                    }
                    else {
                         // all block tags
                         var elements = this.getBlocks();
                         $.each(elements, $.proxy(function (i, elem) {
                              var $el = false;

                              if (elem.tagName === 'TD') return;

                              if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1) {
                                   $el = $(elem);
                              }
                              else {
                                   $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                              }

                              var left = this.normalize($el.css('margin-left')) + this.opts.indentValue;
                              $el.css('margin-left', left + 'px');

                         }, this));
                    }

                    this.selectionRestore();

               }
               // outdent
               else {
                    this.selectionSave();

                    var block = this.getBlock();
                    if (block && block.tagName == 'LI') {
                         // li
                         var elems = this.getBlocks();
                         var index = 0;

                         this.insideOutdent(block, index, elems);
                    }
                    else {
                         // all block tags
                         var elements = this.getBlocks();
                         $.each(elements, $.proxy(function (i, elem) {
                              var $el = false;

                              if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1) {
                                   $el = $(elem);
                              }
                              else {
                                   $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                              }

                              var left = this.normalize($el.css('margin-left')) - this.opts.indentValue;
                              if (left <= 0) {
                                   // linebreaks
                                   if (this.opts.linebreaks === true && typeof ($el.data('tagblock')) !== 'undefined') {
                                        $el.replaceWith($el.html());
                                   }
                                   // all block tags
                                   else {
                                        $el.css('margin-left', '');
                                        this.removeEmptyAttr($el, 'style');
                                   }
                              }
                              else {
                                   $el.css('margin-left', left + 'px');
                              }

                         }, this));
                    }


                    this.selectionRestore();
               }

          },
          insideOutdent: function (li, index, elems) {
               if (li && li.tagName == 'LI') {
                    var $parent = $(li).parent().parent();
                    if ($parent.size() != 0 && $parent[0].tagName == 'LI') {
                         $parent.after(li);
                    }
                    else {
                         if (typeof elems[index] != 'undefined') {
                              li = elems[index];
                              index++;

                              this.insideOutdent(li, index, elems);
                         }
                         else {
                              this.execCommand('insertunorderedlist');
                         }
                    }
               }
          },

          // CLEAN
          cleanEmpty: function (html) {
               var ph = this.placeholderStart(html);
               if (ph !== false) return ph;

               if (this.opts.linebreaks === false) {
                    if (html === '') html = this.opts.emptyHtml;
                    else if (html.search(/^<hr\s?\/?>$/gi) !== -1) html = '<hr>' + this.opts.emptyHtml;
               }

               return html;
          },
          cleanConverters: function (html) {
               // convert div to p
               if (this.opts.convertDivs) html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
               if (this.opts.paragraphy) html = this.cleanParagraphy(html);

               return html;
          },
          cleanConvertProtected: function (html) {
               if (this.opts.templateVars) {
                    html = html.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
                    html = html.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
               }

               html = html.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
               html = html.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
               html = html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');

               // php tags convertation
               if (this.opts.phpTags) html = html.replace(/<\?php([\w\W]*?)\?>/gi, '<section style="display: none;" rel="redactor-php-tag">$1</section>');
               else html = html.replace(/<\?php([\w\W]*?)\?>/gi, '');

               return html;
          },
          cleanReConvertProtected: function (html) {
               if (this.opts.templateVars) {
                    html = html.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
                    html = html.replace(/<!-- template (.*?) -->/gi, '{$1}');
               }

               html = html.replace(/<title type="text\/javascript" style="display: none;" class="redactor-script-tag"(.*?)>([\w\W]*?)<\/title>/gi, '<script$1 type="text/javascript">$2</script>');
               html = html.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
               html = html.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');

               // php tags convertation
               if (this.opts.phpTags) html = html.replace(/<section style="display: none;" rel="redactor-php-tag">([\w\W]*?)<\/section>/gi, '<?php\r\n$1\r\n?>');

               return html;
          },
          cleanRemoveSpaces: function (html, buffer) {
               if (buffer !== false) {
                    var buffer = []
                    var matches = html.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);
                    if (matches === null) matches = [];

                    if (this.opts.phpTags) {
                         var phpMatches = html.match(/<\?php([\w\W]*?)\?>/gi);
                         if (phpMatches) matches = $.merge(matches, phpMatches);
                    }

                    if (matches) {
                         $.each(matches, function (i, s) {
                              html = html.replace(s, 'buffer_' + i);
                              buffer.push(s);
                         });
                    }
               }

               html = html.replace(/\n/g, ' ');
               html = html.replace(/[\t]*/g, '');
               html = html.replace(/\n\s*\n/g, "\n");
               html = html.replace(/^[\s\n]*/g, ' ');
               html = html.replace(/[\s\n]*$/g, ' ');
               html = html.replace(/>\s{2,}</g, '> <'); // between inline tags can be only one space

               html = this.cleanReplacer(html, buffer);

               html = html.replace(/\n\n/g, "\n");

               return html;
          },
          cleanReplacer: function (html, buffer) {
               if (buffer === false) return html;

               $.each(buffer, function (i, s) {
                    html = html.replace('buffer_' + i, s);
               });

               return html;
          },
          cleanRemoveEmptyTags: function (html) {
               html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');

               // remove zero width-space
               html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');

               var etagsInline = ["<b>\\s*</b>", "<b>&nbsp;</b>", "<em>\\s*</em>"]
               var etags = ["<pre></pre>", "<blockquote>\\s*</blockquote>", "<dd></dd>", "<dt></dt>", "<ul></ul>", "<ol></ol>", "<li></li>", "<table></table>", "<tr></tr>", "<span>\\s*<span>", "<span>&nbsp;<span>", "<p>\\s*</p>", "<p></p>", "<p>&nbsp;</p>", "<p>\\s*<br>\\s*</p>", "<div>\\s*</div>", "<div>\\s*<br>\\s*</div>"];

               if (this.opts.removeEmptyTags) {
                    etags = etags.concat(etagsInline);
               }
               else etags = etagsInline;

               var len = etags.length;
               for (var i = 0; i < len; ++i) {
                    html = html.replace(new RegExp(etags[i], 'gi'), "");
               }

               return html;
          },
          cleanParagraphy: function (html) {
               html = $.trim(html);

               if (this.opts.linebreaks === true) return html;
               if (html === '' || html === '<p></p>') return this.opts.emptyHtml;

               html = html + "\n";

               var safes = [];
               var matches = html.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);
               if (matches === null) matches = [];

               var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
               if (commentsMatches) matches = $.merge(matches, commentsMatches);

               if (this.opts.phpTags) {
                    var phpMatches = html.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
                    if (phpMatches) {
                         matches = $.merge(matches, phpMatches);
                    }
               }

               if (matches) {
                    $.each(matches, function (i, s) {
                         safes[i] = s;
                         html = html.replace(s, '{replace' + i + '}\n');
                    });
               }

               html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");

               function R(str, mod, r) {
                    return html.replace(new RegExp(str, mod), r);
               }

               var blocks = '(comment|html|body|head|title|meta|style|script|link|iframe|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)';

               html = R('(<' + blocks + '[^>]*>)', 'gi', "\n$1");
               html = R('(</' + blocks + '>)', 'gi', "$1\n\n");
               html = R("\r\n", 'g', "\n");
               html = R("\r", 'g', "\n");
               html = R("/\n\n+/", 'g', "\n\n");

               var htmls = html.split(new RegExp('\n\s*\n', 'g'), -1);

               html = '';
               for (var i in htmls) {
                    if (htmls.hasOwnProperty(i)) {
                         if (htmls[i].search('{replace') == -1) {
                              html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
                         }
                         else html += htmls[i];
                    }
               }

               // blockquote
               if (html.search(/<blockquote/gi) !== -1) {
                    $.each(html.match(/<blockquote(.*?)>([\w\W]*?)<\/blockquote>/gi), function (i, s) {
                         var str = '';
                         str = s.replace('<p>', '');
                         str = str.replace('</p>', '<br>');
                         html = html.replace(s, str);
                    });
               }

               html = R('<p>\s*</p>', 'gi', '');
               html = R('<p>([^<]+)</(div|address|form)>', 'gi', "<p>$1</p></$2>");
               html = R('<p>\s*(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
               html = R("<p>(<li.+?)</p>", 'gi', "$1");
               html = R('<p>\s*(</?' + blocks + '[^>]*>)', 'gi', "$1");

               html = R('(</?' + blocks + '[^>]*>)\s*</p>', 'gi', "$1");
               html = R('(</?' + blocks + '[^>]*>)\s*<br />', 'gi', "$1");
               html = R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
               html = R("\n</p>", 'gi', '</p>');

               html = R('<li><p>', 'gi', '<li>');
               html = R('</p></li>', 'gi', '</li>');
               html = R('</li><p>', 'gi', '</li>');
               //html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
               /* html = R('</ol><p>', 'gi', '</ol>'); */
               html = R('<p>\t?\n?<p>', 'gi', '<p>');
               html = R('</dt><p>', 'gi', '</dt>');
               html = R('</dd><p>', 'gi', '</dd>');
               html = R('<br></p></blockquote>', 'gi', '</blockquote>');
               html = R('<p>\t*</p>', 'gi', '');


               // restore safes
               $.each(safes, function (i, s) {
                    html = html.replace('{replace' + i + '}', s);
               });

               return $.trim(html);
          },
          cleanConvertInlineTags: function (html) {
               var boldTag = 'strong';
               if (this.opts.boldTag === 'b') boldTag = 'b';

               var italicTag = 'em';
               if (this.opts.italicTag === 'i') italicTag = 'i';

               html = html.replace(/<span style="font-style: italic;">([\w\W]*?)<\/span>/gi, '<' + italicTag + '>$1</' + italicTag + '>');
               html = html.replace(/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gi, '<' + boldTag + '>$1</' + boldTag + '>');

               // bold, italic, del
               if (this.opts.boldTag === 'strong') html = html.replace(/<b>([\w\W]*?)<\/b>/gi, '<strong>$1</strong>');
               else html = html.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>');

               if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
               else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');

               html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');

               if (!/<span(.*?)data-redactor="verified"(.*?)>([\w\W]*?)<\/span>/gi.test(html)) {
                    html = html.replace(/<span(.*?)(?!data-redactor="verified")(.*?)>([\w\W]*?)<\/span>/gi, '<span$1 data-redactor="verified"$2>$3</span>');
               }

               return html;
          },
          cleanStripTags: function (html) {
               if (html == '' || typeof html == 'undefined') return html;

               var allowed = false;
               if (this.opts.allowedTags !== false) allowed = true;

               var arr = allowed === true ? this.opts.allowedTags : this.opts.deniedTags;

               var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
               html = html.replace(tags, function ($0, $1) {
                    if (allowed === true) return $.inArray($1.toLowerCase(), arr) > '-1' ? $0 : '';
                    else return $.inArray($1.toLowerCase(), arr) > '-1' ? '' : $0;
               });

               html = this.cleanConvertInlineTags(html);

               return html;

          },
          cleanSavePreCode: function (html, encode) {
               var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
               if (pre !== null) {
                    $.each(pre, $.proxy(function (i, s) {
                         var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);

                         arr[3] = arr[3].replace(/&nbsp;/g, ' ');
                         if (encode !== false) arr[3] = this.cleanEncodeEntities(arr[3]);

                         html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');

                    }, this));
               }

               return html;
          },
          cleanEncodeEntities: function (str) {
               str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
               return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
          },
          cleanUnverified: function () {
               // label, abbr, mark, meter, code, q, dfn, ins, time, kbd, var

               var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');

               $elem.not('[data-redactor="verified"]').filter('[style*="font-size"][style*="line-height"]')
			.css('font-size', '')
			.css('line-height', '');

               $elem.not('[data-redactor="verified"]').filter('[style*="background-color: transparent;"][style*="line-height"]')
			.css('background-color', '')
			.css('line-height', '');

               $elem.not('[data-redactor="verified"]').filter('[style*="background-color: transparent;"]')
			.css('background-color', '');

               $elem.not('[data-redactor="verified"]').css('line-height', '');

               $.each($elem, $.proxy(function (i, s) {
                    this.removeEmptyAttr(s, 'style');
               }, this));

               // When we paste text in Safari is wrapping inserted div (remove it)
               this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap();
          },
          cleanHtml: function (code) {
               var i = 0,
			codeLength = code.length,
			point = 0,
			start = null,
			end = null,
			tag = '',
			out = '',
			cont = '';

               this.cleanlevel = 0;

               for (; i < codeLength; i++) {
                    point = i;

                    // if no more tags, copy and exit
                    if (-1 == code.substr(i).indexOf('<')) {
                         out += code.substr(i);

                         return this.cleanFinish(out);
                    }

                    // copy verbatim until a tag
                    while (point < codeLength && code.charAt(point) != '<') {
                         point++;
                    }

                    if (i != point) {
                         cont = code.substr(i, point - i);
                         if (!cont.match(/^\s{2,}$/g)) {
                              if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
                              else if ('\n' == cont.charAt(0)) {
                                   out += '\n' + this.cleanGetTabs();
                                   cont = cont.replace(/^\s+/, '');
                              }

                              out += cont;
                         }

                         if (cont.match(/\n/)) out += '\n' + this.cleanGetTabs();
                    }

                    start = point;

                    // find the end of the tag
                    while (point < codeLength && '>' != code.charAt(point)) {
                         point++;
                    }

                    tag = code.substr(start, point - start);
                    i = point;

                    var t;

                    if ('!--' == tag.substr(1, 3)) {
                         if (!tag.match(/--$/)) {
                              while ('-->' != code.substr(point, 3)) {
                                   point++;
                              }
                              point += 2;
                              tag = code.substr(start, point - start);
                              i = point;
                         }

                         if ('\n' != out.charAt(out.length - 1)) out += '\n';

                         out += this.cleanGetTabs();
                         out += tag + '>\n';
                    }
                    else if ('!' == tag[1]) {
                         out = this.placeTag(tag + '>', out);
                    }
                    else if ('?' == tag[1]) {
                         out += tag + '>\n';
                    }
                    else if (t = tag.match(/^<(script|style|pre)/i)) {
                         t[1] = t[1].toLowerCase();
                         tag = this.cleanTag(tag);
                         out = this.placeTag(tag, out);
                         end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);

                         if (end) {
                              cont = code.substr(i + 1, end);
                              i += end;
                              out += cont;
                         }
                    }
                    else {
                         tag = this.cleanTag(tag);
                         out = this.placeTag(tag, out);
                    }
               }

               return this.cleanFinish(out);
          },
          cleanGetTabs: function () {
               var s = '';
               for (var j = 0; j < this.cleanlevel; j++) {
                    s += '\t';
               }

               return s;
          },
          cleanFinish: function (code) {
               code = code.replace(/\n\s*\n/g, '\n');
               code = code.replace(/^[\s\n]*/, '');
               code = code.replace(/[\s\n]*$/, '');
               code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');

               this.cleanlevel = 0;

               return code;
          },
          cleanTag: function (tag) {
               var tagout = '';
               tag = tag.replace(/\n/g, ' ');
               tag = tag.replace(/\s{2,}/g, ' ');
               tag = tag.replace(/^\s+|\s+$/g, ' ');

               var suffix = '';
               if (tag.match(/\/$/)) {
                    suffix = '/';
                    tag = tag.replace(/\/+$/, '');
               }

               var m;
               while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag)) {
                    if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
                    else if (m[1]) tagout += m[1].toLowerCase();

                    tagout += ' ';
                    tag = tag.substr(m[0].length);
               }

               return tagout.replace(/\s*$/, '') + suffix + '>';
          },
          placeTag: function (tag, out) {
               var nl = tag.match(this.cleannewLevel);
               if (tag.match(this.cleanlineBefore) || nl) {
                    out = out.replace(/\s*$/, '');
                    out += '\n';
               }

               if (nl && '/' == tag.charAt(1)) this.cleanlevel--;
               if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
               if (nl && '/' != tag.charAt(1)) this.cleanlevel++;

               out += tag;

               if (tag.match(this.cleanlineAfter) || tag.match(this.cleannewLevel)) {
                    out = out.replace(/ *$/, '');
                    out += '\n';
               }

               return out;
          },

          // ALIGNMENT
          alignmentLeft: function () {
               this.alignmentSet('', 'JustifyLeft');
          },
          alignmentRight: function () {
               this.alignmentSet('right', 'JustifyRight');
          },
          alignmentCenter: function () {
               this.alignmentSet('center', 'JustifyCenter');
          },
          alignmentJustify: function () {
               this.alignmentSet('justify', 'JustifyFull');
          },
          alignmentSet: function (type, cmd) {
               this.bufferSet();

               if (this.oldIE()) {
                    this.document.execCommand(cmd, false, false);
                    return true;
               }

               this.selectionSave();

               var block = this.getBlock();
               if (!block && this.opts.linebreaks) {
                    // one element
                    this.exec('formatBlock', 'blockquote');
                    var newblock = this.getBlock();
                    var block = $('<div data-tagblock="">').html($(newblock).html());
                    $(newblock).replaceWith(block);

                    $(block).css('text-align', type);
                    this.removeEmptyAttr(block, 'style');

                    if (type == '' && typeof ($(block).data('tagblock')) !== 'undefined') {
                         $(block).replaceWith($(block).html());
                    }
               }
               else {
                    var elements = this.getBlocks();
                    $.each(elements, $.proxy(function (i, elem) {
                         var $el = false;

                         if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1) {
                              $el = $(elem);
                         }
                         else {
                              $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                         }

                         if ($el) {
                              $el.css('text-align', type);
                              this.removeEmptyAttr($el, 'style');
                         }

                    }, this));
               }

               this.selectionRestore();

               this.sync();
          },

          // FORMAT
          formatEmpty: function (e) {
               var html = $.trim(this.$editor.html());

               html = html.replace(/<br\s?\/?>/i, '');
               var thtml = html.replace(/<p>\s?<\/p>/gi, '');

               if (html === '' || thtml === '') {
                    e.preventDefault();

                    var node = $(this.opts.emptyHtml).get(0);
                    this.$editor.html(node);
                    this.focus();
               }

               this.sync();
          },
          formatBlocks: function (tag) {
               this.bufferSet();

               var nodes = this.getBlocks();
               this.selectionSave();

               $.each(nodes, $.proxy(function (i, node) {
                    if (node.tagName !== 'LI') {
                         this.formatBlock(tag, node);
                    }

               }, this));

               this.selectionRestore();
               this.sync();
          },
          formatBlock: function (tag, block) {
               if (block === false) block = this.getBlock();
               if (block === false) {
                    if (this.opts.linebreaks === true) this.execCommand('formatblock', tag);
                    return true;
               }

               var contents = '';
               if (tag !== 'pre') {
                    contents = $(block).contents();
               }
               else {
                    //contents = this.cleanEncodeEntities($(block).text());
                    contents = $(block).html();
                    if ($.trim(contents) === '') {
                         contents = '<span id="selection-marker-1"></span>';
                    }
               }

               if (block.tagName === 'PRE') tag = 'p';

               if (this.opts.linebreaks === true && tag === 'p') {
                    $(block).replaceWith($('<div>').append(contents).html() + '<br>');
               }
               else {
                    var node = $('<' + tag + '>').append(contents);
                    $(block).replaceWith(node);
               }
          },
          formatChangeTag: function (fromElement, toTagName, save) {
               if (save !== false) this.selectionSave();

               var newElement = $('<' + toTagName + '/>');
               $(fromElement).replaceWith(function () { return newElement.append($(this).contents()); });

               if (save !== false) this.selectionRestore();

               return newElement;
          },

          // QUOTE
          formatQuote: function () {
               this.bufferSet();

               if (this.opts.linebreaks === false) {
                    this.selectionSave();

                    var blocks = this.getBlocks();
                    if (blocks) {
                         $.each(blocks, $.proxy(function (i, s) {
                              if (s.tagName === 'BLOCKQUOTE') {
                                   this.formatBlock('p', s, false);
                              }
                              else if (s.tagName !== 'LI') {
                                   this.formatBlock('blockquote', s, false);
                              }

                         }, this));
                    }

                    this.selectionRestore();
               }
               // linebreaks
               else {
                    var block = this.getBlock();
                    if (block.tagName === 'BLOCKQUOTE') {
                         this.selectionSave();

                         $(block).replaceWith($(block).html() + '<br>');

                         this.selectionRestore();
                    }
                    else {
                         var wrapper = this.selectionWrap('blockquote');
                         var html = $(wrapper).html();

                         var blocksElemsRemove = ['ul', 'ol', 'table', 'tr', 'tbody', 'thead', 'tfoot', 'dl'];
                         $.each(blocksElemsRemove, function (i, s) {
                              html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
                              html = html.replace(new RegExp('</' + s + '>', 'gi'), '');
                         });

                         var blocksElems = this.opts.blockLevelElements;
                         blocksElems.push('td');
                         $.each(blocksElems, function (i, s) {
                              html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
                              html = html.replace(new RegExp('</' + s + '>', 'gi'), '<br>');
                         });

                         $(wrapper).html(html);
                         this.selectionElement(wrapper);
                         var next = $(wrapper).next();
                         if (next.size() != 0 && next[0].tagName === 'BR') {
                              next.remove();
                         }
                    }
               }

               this.sync();
          },


          // BLOCK CLASS AND STYLE
          blockRemoveAttr: function (attr, value) {
               var nodes = this.getBlocks();
               $(nodes).removeAttr(attr);

               this.sync();
          },
          blockSetAttr: function (attr, value) {
               var nodes = this.getBlocks();
               $(nodes).attr(attr, value);

               this.sync();
          },
          blockRemoveStyle: function (rule) {
               var nodes = this.getBlocks();
               $(nodes).css(rule, '');
               this.removeEmptyAttr(nodes, 'style');

               this.sync();
          },
          blockSetStyle: function (rule, value) {
               var nodes = this.getBlocks();
               $(nodes).css(rule, value);

               this.sync();
          },
          blockRemoveClass: function (className) {
               var nodes = this.getBlocks();
               $(nodes).removeClass(className);
               this.removeEmptyAttr(nodes, 'class');

               this.sync();
          },
          blockSetClass: function (className) {
               var nodes = this.getBlocks();
               $(nodes).addClass(className);

               this.sync();
          },

          // INLINE CLASS AND STYLE
          inlineRemoveClass: function (className) {
               this.selectionSave();

               this.inlineEachNodes(function (node) {
                    $(node).removeClass(className);
                    this.removeEmptyAttr(node, 'class');
               });

               this.selectionRestore();
               this.sync();
          },

          inlineSetClass: function (className) {
               var current = this.getCurrent();
               if (!$(current).hasClass(className)) this.inlineMethods('addClass', className);
          },
          inlineRemoveStyle: function (rule) {
               this.selectionSave();

               this.inlineEachNodes(function (node) {
                    $(node).css(rule, '');
                    this.removeEmptyAttr(node, 'style');
               });

               this.selectionRestore();
               this.sync();
          },
          inlineSetStyle: function (rule, value) {
               this.inlineMethods('css', rule, value);
          },
          inlineRemoveAttr: function (attr) {
               this.selectionSave();

               var range = this.getRange(), node = this.getElement(), nodes = this.getNodes();

               if (range.collapsed || range.startContainer === range.endContainer && node) {
                    nodes = $(node);
               }

               $(nodes).removeAttr(attr);

               this.inlineUnwrapSpan();

               this.selectionRestore();
               this.sync();
          },
          inlineSetAttr: function (attr, value) {
               this.inlineMethods('attr', attr, value);
          },
          inlineMethods: function (type, attr, value) {
               this.bufferSet();
               this.selectionSave();

               var range = this.getRange()
               var el = this.getElement();

               if ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el)) {
                    $(el)[type](attr, value);
               }
               else {
                    this.document.execCommand('fontSize', false, 4);

                    var fonts = this.$editor.find('font');
                    $.each(fonts, $.proxy(function (i, s) {
                         this.inlineSetMethods(type, s, attr, value);

                    }, this));
               }

               this.selectionRestore();

               this.sync();
          },
          inlineSetMethods: function (type, s, attr, value) {
               var parent = $(s).parent(), el;

               if (parent && parent[0].tagName === 'SPAN' && parent[0].attributes.length != 0) {
                    el = parent;
                    $(s).replaceWith($(s).html());
               }
               else {
                    el = $('<span data-redactor="verified" data-redactor-inlineMethods>').append($(s).contents());
                    $(s).replaceWith(el);
               }

               $(el)[type](attr, value);

               return el;
          },
          // Sort elements and execute callback
          inlineEachNodes: function (callback) {
               var range = this.getRange(),
				node = this.getElement(),
				nodes = this.getNodes(),
				collapsed;

               if (range.collapsed || range.startContainer === range.endContainer && node) {
                    nodes = $(node);
                    collapsed = true;
               }

               $.each(nodes, $.proxy(function (i, node) {
                    if (!collapsed && node.tagName !== 'SPAN') {
                         if (node.parentNode.tagName === 'SPAN' && !$(node.parentNode).hasClass('redactor_editor')) {
                              node = node.parentNode;
                         }
                         else return;
                    }
                    callback.call(this, node);

               }, this));
          },
          inlineUnwrapSpan: function () {
               var $spans = this.$editor.find('span[data-redactor-inlineMethods]');

               $.each($spans, $.proxy(function (i, span) {
                    var $span = $(span);

                    if ($span.attr('class') === undefined && $span.attr('style') === undefined) {
                         $span.contents().unwrap();
                    }

               }, this));
          },
          inlineFormat: function (tag) {
               this.selectionSave();

               this.document.execCommand('fontSize', false, 4);

               var fonts = this.$editor.find('font');
               var last;
               $.each(fonts, function (i, s) {
                    var el = $('<' + tag + '/>').append($(s).contents());
                    $(s).replaceWith(el);
                    last = el;
               });

               this.selectionRestore();

               this.sync();
          },
          inlineRemoveFormat: function (tag) {
               this.selectionSave();

               var utag = tag.toUpperCase();
               var nodes = this.getNodes();
               var parent = $(this.getParent()).parent();

               $.each(nodes, function (i, s) {
                    if (s.tagName === utag) this.inlineRemoveFormatReplace(s);
               });

               if (parent && parent[0].tagName === utag) this.inlineRemoveFormatReplace(parent);

               this.selectionRestore();
               this.sync();
          },
          inlineRemoveFormatReplace: function (el) {
               $(el).replaceWith($(el).contents());
          },

          // INSERT
          insertHtml: function (html, sync) {
               var current = this.getCurrent();
               var parent = current.parentNode;

               this.$editor.focus();

               this.bufferSet();

               var $html = $('<div>').append($.parseHTML(html));
               html = $html.html();

               html = this.cleanRemoveEmptyTags(html);

               // Update value
               $html = $('<div>').append($.parseHTML(html));

               var currBlock = this.getBlock();

               if ($html.contents().length == 1) {
                    var htmlTagName = $html.contents()[0].tagName;

                    // If the inserted and received text tags match
                    if (htmlTagName != 'P' && htmlTagName == currBlock.tagName || htmlTagName == 'PRE') {
                         html = $html.text();
                         $html = $('<div>').append(html);
                    }
               }

               // add text in a paragraph
               if (!this.opts.linebreaks && $html.contents().length == 1 && $html.contents()[0].nodeType == 3
				&& (this.getRangeSelectedNodes().length > 2 || (!current || current.tagName == 'BODY' && !parent || parent.tagName == 'HTML'))) {
                    html = '<p>' + html + '</p>';
               }

               // in the quote, we can insert text or links only
               /*
               if (currBlock.tagName == 'BLOCKQUOTE' && html.indexOf('<a') === -1)
               {
               this.insertText(html);
               }
               else
               */
               if ($html.contents().length > 1 && currBlock
					|| $html.contents().is('p, :header, ul, ol, div, table, blockquote, pre, address, section, header, footer, aside, article')) {
                    if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
                    else this.document.execCommand('inserthtml', false, html);
               }
               else this.insertHtmlAdvanced(html, false);

               if (this.selectall) {
                    this.window.setTimeout($.proxy(function () {
                         if (!this.opts.linebreaks) this.selectionEnd(this.$editor.contents().last());
                         else this.focusEnd();

                    }, this), 1);
               }

               this.observeStart();

               if (sync !== false) this.sync();
          },
          insertHtmlAdvanced: function (html, sync) {
               var sel = this.getSelection();

               if (sel.getRangeAt && sel.rangeCount) {
                    var range = sel.getRangeAt(0);
                    range.deleteContents();

                    var el = this.document.createElement('div');
                    el.innerHTML = html;
                    var frag = this.document.createDocumentFragment(), node, lastNode;
                    while ((node = el.firstChild)) {
                         lastNode = frag.appendChild(node);
                    }

                    range.insertNode(frag);

                    if (lastNode) {
                         range = range.cloneRange();
                         range.setStartAfter(lastNode);
                         range.collapse(true);
                         sel.removeAllRanges();
                         sel.addRange(range);
                    }
               }

               if (sync !== false) {
                    this.sync();
               }

          },
          insertText: function (html) {
               var $html = $($.parseHTML(html));

               if ($html.length) html = $html.text();

               this.$editor.focus();

               if (this.browser('msie')) this.document.selection.createRange().pasteHTML(html);
               else this.document.execCommand('inserthtml', false, html);

               this.sync();
          },
          insertNode: function (node) {
               node = node[0] || node;
               var sel = this.getSelection();
               if (sel.getRangeAt && sel.rangeCount) {
                    // with delete contents
                    range = sel.getRangeAt(0);
                    range.deleteContents();
                    range.insertNode(node);
                    range.setEndAfter(node);
                    range.setStartAfter(node);
                    sel.removeAllRanges();
                    sel.addRange(range);
               }
          },
          insertNodeToCaretPositionFromPoint: function (e, node) {
               var range;
               var x = e.clientX, y = e.clientY;
               if (this.document.caretPositionFromPoint) {
                    var pos = this.document.caretPositionFromPoint(x, y);
                    range = this.getRange();
                    range.setStart(pos.offsetNode, pos.offset);
                    range.collapse(true);
                    range.insertNode(node);
               }
               else if (this.document.caretRangeFromPoint) {
                    range = this.document.caretRangeFromPoint(x, y);
                    range.insertNode(node);
               }
               else if (typeof document.body.createTextRange != "undefined") {
                    range = this.document.body.createTextRange();
                    range.moveToPoint(x, y);
                    var endRange = range.duplicate();
                    endRange.moveToPoint(x, y);
                    range.setEndPoint("EndToEnd", endRange);
                    range.select();
               }

          },
          insertAfterLastElement: function (element) {
               if (this.isEndOfElement()) {

                    if ($($.trim(this.$editor.html())).get(0) != $.trim(element)
				&& this.$editor.contents().last()[0] !== element) return false;

                    this.bufferSet();

                    if (this.opts.linebreaks === false) {
                         var node = $(this.opts.emptyHtml);
                         $(element).after(node);
                         this.selectionStart(node);
                    }
                    else {
                         var node = $('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>', this.document)[0];
                         $(element).after(node);
                         $(node).after(this.opts.invisibleSpace);
                         this.selectionRestore();
                    }
               }
          },
          insertLineBreak: function () {
               this.selectionSave();
               this.$editor.find('#selection-marker-1').before('<br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
               this.selectionRestore();
          },
          insertDoubleLineBreak: function () {
               this.selectionSave();
               this.$editor.find('#selection-marker-1').before('<br><br>' + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
               this.selectionRestore();
          },
          replaceLineBreak: function (element) {
               //var node = this.document.createTextNode('\uFEFF');
               var node = $('<br>' + this.opts.invisibleSpace);
               $(element).replaceWith(node);
               this.selectionStart(node);
          },

          // PASTE
          pasteClean: function (html) {
               html = this.callback('pasteBefore', false, html);

               if (this.opts.pastePlainText) {
                    var tmp = this.document.createElement('div');

                    html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');

                    tmp.innerHTML = html;
                    html = tmp.textContent || tmp.innerText;

                    html = html.replace('\n', '<br>');
                    html = this.cleanParagraphy(html);

                    this.pasteInsert(html);
                    return false;
               }

               // clean up pre
               if (this.currentOrParentIs('PRE')) {
                    html = this.pastePre(html);
                    this.pasteInsert(html);
                    return true;
               }

               // ms word list
               html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><p$2</p>');
               html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<p$2</p></ul>');

               // remove comments and php tags
               html = html.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, '');

               // remove nbsp
               html = html.replace(/(&nbsp;){2,}/gi, '&nbsp;');
               html = html.replace(/&nbsp;/gi, ' ');

               // remove google docs marker
               html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
               html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");

               // strip tags
               html = this.cleanStripTags(html);

               // prevert
               html = html.replace(/<td><\/td>/gi, '[td]');
               html = html.replace(/<td>&nbsp;<\/td>/gi, '[td]');
               html = html.replace(/<td><br><\/td>/gi, '[td]');
               html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
               html = html.replace(/<iframe(.*?)>([\w\W]*?)<\/iframe>/gi, '[iframe$1]$2[/iframe]');
               html = html.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]');
               html = html.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]');
               html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
               html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
               html = html.replace(/<param(.*?)>/gi, '[param$1]');
               html = html.replace(/<img(.*?)style="(.*?)"(.*?)>/gi, '[img$1$3]');
               html = html.replace(/<img(.*?)>/gi, '[img$1]');

               // remove classes
               html = html.replace(/ class="(.*?)"/gi, '');

               // remove all attributes
               html = html.replace(/<(\w+)([\w\W]*?)>/gi, '<$1>');

               // remove empty
               html = html.replace(/<[^\/>][^>]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');

               html = html.replace(/<div>\s*?\t*?\n*?(<ul>|<ol>|<p>)/gi, '$1');

               // revert
               html = html.replace(/\[td\]/gi, '<td>&nbsp;</td>');
               html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
               html = html.replace(/\[iframe(.*?)\]([\w\W]*?)\[\/iframe\]/gi, '<iframe$1>$2</iframe>');
               html = html.replace(/\[video(.*?)\]([\w\W]*?)\[\/video\]/gi, '<video$1>$2</video>');
               html = html.replace(/\[audio(.*?)\]([\w\W]*?)\[\/audio\]/gi, '<audio$1>$2</audio>');
               html = html.replace(/\[embed(.*?)\]([\w\W]*?)\[\/embed\]/gi, '<embed$1>$2</embed>');
               html = html.replace(/\[object(.*?)\]([\w\W]*?)\[\/object\]/gi, '<object$1>$2</object>');
               html = html.replace(/\[param(.*?)\]/gi, '<param$1>');
               html = html.replace(/\[img(.*?)\]/gi, '<img$1>');

               // convert div to p
               if (this.opts.convertDivs) {
                    html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p>$2</p>');
                    html = html.replace(/<\/div><p>/gi, '<p>');
                    html = html.replace(/<\/p><\/div>/gi, '</p>');
               }

               if (this.currentOrParentIs('LI')) {
                    html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
               }
               else {
                    html = this.cleanParagraphy(html);
               }


               // remove span
               html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');

               // remove empty
               html = html.replace(/<img>/gi, '');
               html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');

               html = html.replace(/\n{3,}/gi, '\n');

               // remove dirty p
               html = html.replace(/<p><p>/gi, '<p>');
               html = html.replace(/<\/p><\/p>/gi, '</p>');

               html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
               html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');

               if (this.opts.linebreaks === true) {
                    html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
               }

               // remove empty finally
               html = html.replace(/<[^\/>][^>][^img|param|source]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');

               // remove safari local images
               html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');

               // FF specific
               this.pasteClipboardMozilla = false;
               if (this.browser('mozilla')) {
                    if (this.opts.clipboardUpload) {
                         var matches = html.match(/<img src="data:image(.*?)"(.*?)>/gi);
                         if (matches !== null) {
                              this.pasteClipboardMozilla = matches;
                              for (k in matches) {
                                   var img = matches[k].replace('<img', '<img data-mozilla-paste-image="' + k + '" ');
                                   html = html.replace(matches[k], img);
                              }
                         }
                    }

                    // FF fix
                    while (/<br>$/gi.test(html)) {
                         html = html.replace(/<br>$/gi, '');
                    }
               }

               // bullets
               html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');

               // ie inserts a blank font tags when pasting
               while (/<font>([\w\W]*?)<\/font>/gi.test(html)) {
                    html = html.replace(/<font>([\w\W]*?)<\/font>/gi, '$1');
               }


               this.pasteInsert(html);

          },
          pastePre: function (s) {
               s = s.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');

               var tmp = this.document.createElement('div');
               tmp.innerHTML = s;
               return this.cleanEncodeEntities(tmp.textContent || tmp.innerText);
          },
          pasteInsert: function (html) {
               if (this.selectall) {
                    if (!this.opts.linebreaks) this.$editor.html(this.opts.emptyHtml);
                    else this.$editor.html('');

                    this.$editor.focus();
               }

               html = this.callback('pasteAfter', false, html);

               this.insertHtml(html);

               this.selectall = false;
               setTimeout($.proxy(function () {
                    rtePaste = false;

                    // FF specific
                    if (this.browser('mozilla')) {
                         this.$editor.find('p:empty').remove()
                    }
                    if (this.pasteClipboardMozilla !== false) {
                         this.pasteClipboardUploadMozilla();
                    }

               }, this), 100);

               if (this.opts.autoresize) $(this.document.body).scrollTop(this.saveScroll);
               else this.$editor.scrollTop(this.saveScroll);
          },

          pasteClipboardUploadMozilla: function () {
               var imgs = this.$editor.find('img[data-mozilla-paste-image]');
               $.each(imgs, $.proxy(function (i, s) {
                    var $s = $(s);
                    var arr = s.src.split(",");
                    var data = arr[1]; // raw base64
                    var contentType = arr[0].split(";")[0].split(":")[1];

                    $.post(this.opts.clipboardUploadUrl, {
                         contentType: contentType,
                         data: data
                    },
				$.proxy(function (data) {
				     var json = $.parseJSON(data);
				     $s.attr('src', json.filelink);
				     $s.removeAttr('data-mozilla-paste-image');

				     this.sync();

				     // upload callback
				     this.callback('imageUpload', $s, json);

				}, this));

               }, this));
          },
          pasteClipboardUpload: function (e) {
               var result = e.target.result;
               var arr = result.split(",");
               var data = arr[1]; // raw base64
               var contentType = arr[0].split(";")[0].split(":")[1];

               if (this.opts.clipboardUpload) {
                    $.post(this.opts.clipboardUploadUrl, {
                         contentType: contentType,
                         data: data
                    },
				$.proxy(function (data) {
				     var json = $.parseJSON(data);

				     var html = '<img src="' + json.filelink + '" id="clipboard-image-marker" />';
				     this.execCommand('inserthtml', html, false);

				     var image = $(this.$editor.find('img#clipboard-image-marker'));

				     if (image.length) image.removeAttr('id');
				     else image = false;

				     this.sync();

				     // upload callback
				     if (image) {
				          this.callback('imageUpload', image, json);
				     }


				}, this));
               }
               else {
                    this.insertHtml('<img src="' + result + '" />');
               }
          },

          // BUFFER
          bufferSet: function (html) {
               if (html !== undefined) this.opts.buffer.push(html);
               else {
                    this.selectionSave();
                    this.opts.buffer.push(this.$editor.html());
                    this.selectionRemoveMarkers('buffer');
               }
          },
          bufferUndo: function () {
               if (this.opts.buffer.length === 0) {
                    this.$editor.focus();
                    return;
               }

               // rebuffer
               this.selectionSave();
               this.opts.rebuffer.push(this.$editor.html());
               this.selectionRestore(false, true);

               this.$editor.html(this.opts.buffer.pop());

               this.selectionRestore();
               setTimeout($.proxy(this.observeStart, this), 100);
          },
          bufferRedo: function () {
               if (this.opts.rebuffer.length === 0) {
                    this.$editor.focus();
                    return false;
               }

               // buffer
               this.selectionSave();
               this.opts.buffer.push(this.$editor.html());
               this.selectionRestore(false, true);

               this.$editor.html(this.opts.rebuffer.pop());
               this.selectionRestore(true);
               setTimeout($.proxy(this.observeStart, this), 4);
          },


          // OBSERVE
          observeStart: function () {
               this.observeImages();
               this.observeTables();

               if (this.opts.observeLinks) this.observeLinks();
          },
          observeLinks: function () {
               this.$editor.find('a').on('click', $.proxy(this.linkObserver, this));
               this.$editor.on('click.redactor', $.proxy(function (e) {
                    this.linkObserverTooltipClose(e);

               }, this));
          },
          observeTables: function () {
               this.$editor.find('table').on('click', $.proxy(this.tableObserver, this));
          },
          observeImages: function () {
               if (this.opts.observeImages === false) return false;

               this.$editor.find('img').each($.proxy(function (i, elem) {
                    if (this.browser('msie')) $(elem).attr('unselectable', 'on');
                    this.imageResize(elem);

               }, this));
          },
          linkObserver: function (e) {
               var $link = $(e.target);
               var pos = $link.offset();
               if (this.opts.iframe) {
                    var posFrame = this.$frame.offset();
                    pos.top = posFrame.top + (pos.top - $(this.document).scrollTop());
                    pos.left += posFrame.left;
               }

               var tooltip = $('<span class="redactor-link-tooltip"></span>');

               var href = $link.attr('href');
               if (href.length > 24) href = href.substring(0, 24) + '...';

               var aLink = $('<a href="' + $link.attr('href') + '" target="_blank">' + href + '</a>').on('click', $.proxy(function (e) {
                    this.linkObserverTooltipClose(false);
               }, this));

               var aEdit = $('<a href="#">' + this.opts.curLang.edit + '</a>').on('click', $.proxy(function (e) {
                    e.preventDefault();
                    this.linkShow();
                    this.linkObserverTooltipClose(false);

               }, this));

               var aUnlink = $('<a href="#">' + this.opts.curLang.unlink + '</a>').on('click', $.proxy(function (e) {
                    e.preventDefault();
                    this.execCommand('unlink');
                    this.linkObserverTooltipClose(false);

               }, this));


               tooltip.append(aLink);
               tooltip.append(' | ');
               tooltip.append(aEdit);
               tooltip.append(' | ');
               tooltip.append(aUnlink);
               tooltip.css({
                    top: (pos.top + 20) + 'px',
                    left: pos.left + 'px'
               });

               $('.redactor-link-tooltip').remove();
               $('body').append(tooltip);
          },
          linkObserverTooltipClose: function (e) {
               if (e !== false && e.target.tagName == 'A') return false;
               $('.redactor-link-tooltip').remove();
          },


          // SELECTION
          getSelection: function () {
               if (!this.opts.rangy) return this.document.getSelection();
               else // rangy
               {
                    if (!this.opts.iframe) return rangy.getSelection();
                    else return rangy.getSelection(this.$frame[0]);
               }
          },
          getRange: function () {
               if (!this.opts.rangy) {
                    if (this.document.getSelection) {
                         var sel = this.document.getSelection();
                         if (sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
                    }

                    return this.document.createRange();
               }
               else // rangy
               {
                    if (!this.opts.iframe) return rangy.createRange();
                    else return rangy.createRange(this.iframeDoc());
               }
          },
          selectionElement: function (node) {
               this.setCaret(node);
          },
          selectionStart: function (node) {
               this.selectionSet(node[0] || node, 0, null, 0);
          },
          selectionEnd: function (node) {
               this.selectionSet(node[0] || node, 1, null, 1);
          },
          selectionSet: function (orgn, orgo, focn, foco) {
               if (focn == null) focn = orgn;
               if (foco == null) foco = orgo;

               var sel = this.getSelection();
               if (!sel) return;

               var range = this.getRange();
               range.setStart(orgn, orgo);
               range.setEnd(focn, foco);

               try {
                    sel.removeAllRanges();
               } catch (e) { }

               sel.addRange(range);
          },
          selectionWrap: function (tag) {
               tag = tag.toLowerCase();

               var block = this.getBlock();
               if (block) {
                    var wrapper = this.formatChangeTag(block, tag);
                    this.sync();
                    return wrapper;
               }

               var sel = this.getSelection();
               var range = sel.getRangeAt(0);
               var wrapper = document.createElement(tag);
               wrapper.appendChild(range.extractContents());
               range.insertNode(wrapper);

               this.selectionElement(wrapper);

               return wrapper;
          },
          selectionAll: function () {
               var range = this.getRange();
               range.selectNodeContents(this.$editor[0]);

               var sel = this.getSelection();
               sel.removeAllRanges();
               sel.addRange(range);
          },
          selectionRemove: function () {
               this.getSelection().removeAllRanges();
          },
          getCaretOffset: function (element) {
               var caretOffset = 0;

               var range = this.getRange();
               var preCaretRange = range.cloneRange();
               preCaretRange.selectNodeContents(element);
               preCaretRange.setEnd(range.endContainer, range.endOffset);
               caretOffset = $.trim(preCaretRange.toString()).length;

               return caretOffset;
          },
          getCaretOffsetRange: function () {
               return new Range(this.getSelection().getRangeAt(0));
          },
          setCaret: function (el, start, end) {
               if (typeof end === 'undefined') end = start;
               el = el[0] || el;

               var range = this.getRange();
               range.selectNodeContents(el);

               var textNodes = this.getTextNodesIn(el);
               var foundStart = false;
               var charCount = 0, endCharCount;

               if (textNodes.length == 1 && start) {
                    range.setStart(textNodes[0], start);
                    range.setEnd(textNodes[0], end);
               }
               else {
                    for (var i = 0, textNode; textNode = textNodes[i++]; ) {
                         endCharCount = charCount + textNode.length;
                         if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length))) {
                              range.setStart(textNode, start - charCount);
                              foundStart = true;
                         }

                         if (foundStart && end <= endCharCount) {
                              range.setEnd(textNode, end - charCount);
                              break;
                         }

                         charCount = endCharCount;
                    }
               }

               var sel = this.getSelection();
               sel.removeAllRanges();
               sel.addRange(range);
          },
          getTextNodesIn: function (node) {
               var textNodes = [];

               if (node.nodeType == 3) textNodes.push(node);
               else {
                    var children = node.childNodes;
                    for (var i = 0, len = children.length; i < len; ++i) {
                         textNodes.push.apply(textNodes, this.getTextNodesIn(children[i]));
                    }
               }

               return textNodes;
          },

          // SAVE & RESTORE
          selectionSave: function () {
               if (!this.isFocused()) this.$editor.focus();

               if (!this.opts.rangy) {
                    this.selectionCreateMarker(this.getRange());
               }
               // rangy
               else {
                    this.savedSel = rangy.saveSelection();
               }
          },
          selectionCreateMarker: function (range, remove) {
               if (!range) return;

               var node1 = $('<span id="selection-marker-1" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
               var node2 = $('<span id="selection-marker-2" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];

               if (range.collapsed === true) {
                    this.selectionSetMarker(range, node1, true);
               }
               else {
                    this.selectionSetMarker(range, node1, true);
                    this.selectionSetMarker(range, node2, false);
               }

               this.savedSel = this.$editor.html();

               this.selectionRestore(false, false);
          },
          selectionSetMarker: function (range, node, type) {
               var boundaryRange = range.cloneRange();

               boundaryRange.collapse(type);

               boundaryRange.insertNode(node);
               boundaryRange.detach();
          },
          selectionRestore: function (replace, remove) {
               if (!this.opts.rangy) {
                    if (replace === true && this.savedSel) {
                         this.$editor.html(this.savedSel);
                    }

                    var node1 = this.$editor.find('span#selection-marker-1');
                    var node2 = this.$editor.find('span#selection-marker-2');

                    if (this.browser('mozilla')) {
                         this.$editor.focus();
                    }
                    else if (!this.isFocused()) {
                         this.$editor.focus();
                    }

                    if (node1.length != 0 && node2.length != 0) {
                         this.selectionSet(node1[0], 0, node2[0], 0);
                    }
                    else if (node1.length != 0) {
                         this.selectionSet(node1[0], 0, null, 0);
                    }

                    if (remove !== false) {
                         this.selectionRemoveMarkers();
                         this.savedSel = false;
                    }
               }
               // rangy
               else {
                    rangy.restoreSelection(this.savedSel);
               }
          },
          selectionRemoveMarkers: function (type) {
               if (!this.opts.rangy) {
                    $.each(this.$editor.find('span.redactor-selection-marker'), function () {
                         var html = $.trim($(this).html().replace(/[^\u0000-\u1C7F]/g, ''));
                         if (html == '') {
                              $(this).remove();
                         }
                         else {
                              $(this).removeAttr('class').removeAttr('id');
                         }
                    });
               }
               // rangy
               else {
                    rangy.removeMarkers(this.savedSel);
               }
          },
          // GET ELEMENTS
          getCurrent: function () {
               var el = false;
               var sel = this.getSelection();

               if (sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;

               return this.isParentRedactor(el);
          },
          getParent: function (elem) {
               elem = elem || this.getCurrent();
               if (elem) return this.isParentRedactor($(elem).parent()[0]);
               else return false;
          },
          getBlock: function (node) {
               if (typeof node === 'undefined') node = this.getCurrent();

               while (node) {
                    if (this.nodeTestBlocks(node)) {
                         if ($(node).hasClass('redactor_editor')) return false;
                         return node;
                    }

                    node = node.parentNode;
               }

               return false;
          },
          getBlocks: function (nodes) {
               var newnodes = [];
               if (typeof nodes == 'undefined') {
                    var range = this.getRange();
                    if (range && range.collapsed === true) return [this.getBlock()];
                    var nodes = this.getNodes(range);
               }

               $.each(nodes, $.proxy(function (i, node) {
                    if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
                    if (this.nodeTestBlocks(node)) newnodes.push(node);

               }, this));

               if (newnodes.length === 0) newnodes = [this.getBlock()];

               return newnodes;
          },
          nodeTestBlocks: function (node) {
               return node.nodeType == 1 && this.rTestBlock.test(node.nodeName);
          },
          tagTestBlock: function (tag) {
               return this.rTestBlock.test(tag);
          },
          getSelectedNodes: function (range) {
               if (typeof range == 'undefined' || range == false) var range = this.getRange()
               if (range && range.collapsed === true) {
                    return [this.getCurrent()];
               }

               var sel = this.getSelection();
               try {
                    var frag = sel.getRangeAt(0).cloneContents();
               }
               catch (e) {
                    return (false);
               }

               var tempspan = this.document.createElement("span");
               tempspan.appendChild(frag);

               window.selnodes = tempspan.childNodes;

               var len = selnodes.length;
               var output = [];
               for (var i = 0, u = len; i < u; i++) {
                    output.push(selnodes[i]);
               }

               if (output.length == 0) output.push(this.getCurrent());

               return output;
          },
          getNodes: function (range, tag) {
               if (this.opts.linebreaks) {
                    return this.getSelectedNodes(range);
               }


               if (typeof range == 'undefined' || range == false) var range = this.getRange();
               if (range && range.collapsed === true) {
                    if (typeof tag === 'undefined' && this.tagTestBlock(tag)) {
                         var block = this.getBlock();
                         if (block.tagName == tag) return [block];
                         else return [];
                    }
                    else {
                         return [this.getCurrent()];
                    }
               }

               var nodes = [], finalnodes = [];

               var sel = this.document.getSelection();
               if (!sel.isCollapsed) nodes = this.getRangeSelectedNodes(sel.getRangeAt(0));

               $.each(nodes, $.proxy(function (i, node) {
                    if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;

                    if (typeof tag === 'undefined') {
                         if ($.trim(node.textContent) != '') {
                              finalnodes.push(node);
                         }
                    }
                    else if (node.tagName == tag) {
                         finalnodes.push(node);
                    }

               }, this));

               if (finalnodes.length == 0) {
                    if (typeof tag === 'undefined' && this.tagTestBlock(tag)) {
                         var block = this.getBlock();
                         if (block.tagName == tag) return finalnodes.push(block);
                         else return [];
                    }
                    else {
                         finalnodes.push(this.getCurrent());
                    }
               }

               return finalnodes;
          },
          getElement: function (node) {
               if (!node) node = this.getCurrent();
               while (node) {
                    if (node.nodeType == 1) {
                         if ($(node).hasClass('redactor_editor')) return false;
                         return node;
                    }

                    node = node.parentNode;
               }

               return false;
          },
          getRangeSelectedNodes: function (range) {
               range = range || this.getRange();
               var node = range.startContainer;
               var endNode = range.endContainer;

               if (node == endNode) return [node];

               var rangeNodes = [];
               while (node && node != endNode) {
                    rangeNodes.push(node = this.nextNode(node));
               }

               node = range.startContainer;
               while (node && node != range.commonAncestorContainer) {
                    rangeNodes.unshift(node);
                    node = node.parentNode;
               }

               return rangeNodes;
          },
          nextNode: function (node) {
               if (node.hasChildNodes()) return node.firstChild;
               else {
                    while (node && !node.nextSibling) {
                         node = node.parentNode;
                    }

                    if (!node) return null;
                    return node.nextSibling;
               }
          },


          // GET SELECTION HTML OR TEXT
          getSelectionText: function () {
               return this.getSelection().toString();
          },
          getSelectionHtml: function () {
               var html = '';

               var sel = this.getSelection();
               if (sel.rangeCount) {
                    var container = this.document.createElement("div");
                    var len = sel.rangeCount;
                    for (var i = 0; i < len; ++i) {
                         container.appendChild(sel.getRangeAt(i).cloneContents());
                    }

                    html = container.innerHTML;
               }

               return this.syncClean(html);
          },

          // TABLE
          tableShow: function () {
               this.selectionSave();

               this.modalInit(this.opts.curLang.table, this.opts.modal_table, 300, $.proxy(function () {
                    $('#redactor_insert_table_btn').click($.proxy(this.tableInsert, this));

                    setTimeout(function () {
                         $('#redactor_table_rows').focus();

                    }, 200);

               }, this));
          },
          tableInsert: function () {
               var rows = $('#redactor_table_rows').val(),
				columns = $('#redactor_table_columns').val(),
				$table_box = $('<div></div>'),
				tableId = Math.floor(Math.random() * 99999),
				$table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
				i, $row, z, $column;

               for (i = 0; i < rows; i++) {
                    $row = $('<tr></tr>');

                    for (z = 0; z < columns; z++) {
                         $column = $('<td>' + this.opts.invisibleSpace + '</td>');

                         // set the focus to the first td
                         if (i === 0 && z === 0) $column.append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');

                         $($row).append($column);
                    }

                    $table.append($row);
               }

               $table_box.append($table);
               var html = $table_box.html();

               this.modalClose();
               this.selectionRestore();

               var current = this.getBlock() || this.getCurrent();
               if (current && current.tagName != 'BODY') {
                    $(current).after(html)
               }
               else {
                    this.insertHtmlAdvanced(html, false);

               }

               this.selectionRestore();

               var table = this.$editor.find('#table' + tableId);
               this.tableObserver(table);
               this.buttonActiveObserver();

               table.find('span#selection-marker-1').remove();
               table.removeAttr('id');

               this.sync();
          },
          tableObserver: function (e) {
               this.$table = $(e.target || e).closest('table');

               this.$tbody = $(e.target).closest('tbody');
               this.$thead = this.$table.find('thead');

               this.$current_td = $(e.target || this.$table.find('td').first());
               this.$current_tr = $(e.target || this.$table.find('tr').first()).closest('tr');
          },
          tableDeleteTable: function () {
               this.bufferSet();

               if (!this.$table) return;

               this.$table.remove();
               this.$table = false;
               this.sync();

          },
          tableDeleteRow: function () {
               this.bufferSet();

               if (!this.$current_tr) return;

               // Set the focus correctly
               var $focusTR = this.$current_tr.prev().length ? this.$current_tr.prev() : this.$current_tr.next();
               if ($focusTR.length) {
                    var $focusTD = $focusTR.children('td').first();
                    if ($focusTD.length) {
                         $focusTD.prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
                         this.selectionRestore();
                    }
               }

               this.$current_tr.remove();
               this.sync();
          },
          tableDeleteColumn: function () {
               this.bufferSet();
               var index = this.$current_td.get(0).cellIndex;

               // Set the focus correctly
               this.$table.find('tr').each($.proxy(function (i, elem) {
                    var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
                    if (i === 0) {
                         $(elem).find('td').eq(focusIndex).prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
                         this.selectionRestore();
                    }

                    $(elem).find('td').eq(index).remove();

               }, this));

               this.sync();
          },
          tableAddHead: function () {
               this.bufferSet();

               if (this.$table.find('thead').size() !== 0) this.tableDeleteHead();
               else {
                    var tr = this.$table.find('tr').first().clone();
                    tr.find('td').html(this.opts.invisibleSpace);
                    this.$thead = $('<thead></thead>');
                    this.$thead.append(tr);
                    this.$table.prepend(this.$thead);

                    this.sync();
               }
          },
          tableDeleteHead: function () {
               this.bufferSet();

               $(this.$thead).remove();
               this.$thead = false;

               this.sync();
          },
          tableAddRowAbove: function () {
               this.tableAddRow('before');
          },
          tableAddRowBelow: function () {
               this.tableAddRow('after');
          },
          tableAddColumnLeft: function () {
               this.tableAddColumn('before');
          },
          tableAddColumnRight: function () {
               this.tableAddColumn('after');
          },
          tableAddRow: function (type) {
               this.bufferSet();

               var new_tr = this.$current_tr.clone();
               new_tr.find('td').html(this.opts.invisibleSpace);

               if (type === 'after') this.$current_tr.after(new_tr);
               else this.$current_tr.before(new_tr);

               this.sync();
          },
          tableAddColumn: function (type) {
               this.bufferSet();

               var index = 0;

               this.$current_tr.find('td').each($.proxy(function (i, elem) {
                    if ($(elem)[0] === this.$current_td[0]) index = i;

               }, this));

               this.$table.find('tr').each($.proxy(function (i, elem) {
                    var $current = $(elem).find('td').eq(index);

                    var td = $current.clone();
                    td.html(this.opts.invisibleSpace);

                    type === 'after' ? $current.after(td) : $current.before(td);

               }, this));

               this.sync();
          },

          // VIDEO
          videoShow: function () {
               this.selectionSave();

               this.modalInit(this.opts.curLang.video, this.opts.modal_video, 600, $.proxy(function () {
                    $('#redactor_insert_video_btn').click($.proxy(this.videoInsert, this));

                    setTimeout(function () {
                         $('#redactor_insert_video_area').focus();

                    }, 200);

               }, this));
          },
          videoInsert: function () {
               var data = $('#redactor_insert_video_area').val();
               data = this.cleanStripTags(data);

               this.selectionRestore();

               var current = this.getBlock() || this.getCurrent();

               if (current) $(current).after(data)
               else this.insertHtmlAdvanced(data, false);

               this.sync();
               this.modalClose();
          },

          // LINK
          linkShow: function () {
               this.selectionSave();

               var callback = $.proxy(function () {
                    this.insert_link_node = false;

                    var sel = this.getSelection();
                    var url = '', text = '', target = '';

                    var elem = this.getParent();
                    var par = $(elem).parent().get(0);
                    if (par && par.tagName === 'A') {
                         elem = par;
                    }

                    if (elem && elem.tagName === 'A') {
                         url = elem.href;
                         text = $(elem).text();
                         target = elem.target;

                         this.insert_link_node = elem;
                    }
                    else text = sel.toString();

                    $('.redactor_link_text').val(text);

                    var thref = self.location.href.replace(/\/$/i, '');
                    var turl = url.replace(thref, '');

                    // remove host from href
                    if (this.opts.linkProtocol === false) {
                         var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
                         turl = turl.replace(re, '');
                    }

                    var tabs = $('#redactor_tabs').find('a');

                    if (this.opts.linkEmail === false) tabs.eq(1).remove();
                    if (this.opts.linkAnchor === false) tabs.eq(2).remove();
                    if (this.opts.linkEmail === false && this.opts.linkAnchor === false) {
                         $('#redactor_tabs').remove();
                         $('#redactor_link_url').val(turl);
                    }
                    else {
                         if (url.search('mailto:') === 0) {
                              this.modalSetTab.call(this, 2);

                              $('#redactor_tab_selected').val(2);
                              $('#redactor_link_mailto').val(url.replace('mailto:', ''));
                         }
                         else if (turl.search(/^#/gi) === 0) {
                              this.modalSetTab.call(this, 3);

                              $('#redactor_tab_selected').val(3);
                              $('#redactor_link_anchor').val(turl.replace(/^#/gi, ''));
                         }
                         else {
                              $('#redactor_link_url').val(turl);
                         }
                    }

                    if (target === '_blank') $('#redactor_link_blank').prop('checked', true);

                    $('#redactor_insert_link_btn').click($.proxy(this.linkProcess, this));

                    setTimeout(function () {
                         $('#redactor_link_url').focus();

                    }, 200);

               }, this);

               this.modalInit(this.opts.curLang.link, this.opts.modal_link, 460, callback);

          },
          linkProcess: function () {
               var tab_selected = $('#redactor_tab_selected').val();
               var link = '', text = '', target = '', targetBlank = '';

               // url
               if (tab_selected === '1') {
                    link = $('#redactor_link_url').val();
                    text = $('#redactor_link_url_text').val();

                    if ($('#redactor_link_blank').prop('checked')) {
                         target = ' target="_blank"';
                         targetBlank = '_blank';
                    }

                    // test url (add protocol)
                    var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}';
                    var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
                    var re2 = new RegExp('^' + pattern, 'i');

                    if (link.search(re) == -1 && link.search(re2) == 0 && this.opts.linkProtocol) {
                         link = this.opts.linkProtocol + link;
                    }
               }
               // mailto
               else if (tab_selected === '2') {
                    link = 'mailto:' + $('#redactor_link_mailto').val();
                    text = $('#redactor_link_mailto_text').val();
               }
               // anchor
               else if (tab_selected === '3') {
                    link = '#' + $('#redactor_link_anchor').val();
                    text = $('#redactor_link_anchor_text').val();
               }

               text = text.replace(/<|>/g, '');
               this.linkInsert('<a href="' + link + '"' + target + '>' + text + '</a>', $.trim(text), link, targetBlank);

          },
          linkInsert: function (a, text, link, target) {
               this.selectionRestore();

               if (text !== '') {
                    if (this.insert_link_node) {
                         this.bufferSet();

                         $(this.insert_link_node).text(text).attr('href', link);

                         if (target !== '') $(this.insert_link_node).attr('target', target);
                         else $(this.insert_link_node).removeAttr('target');

                         this.sync();
                    }
                    else {
                         this.exec('inserthtml', a);
                    }
               }

               this.modalClose();
          },

          // FILE
          fileShow: function () {

               this.selectionSave();

               var callback = $.proxy(function () {
                    var sel = this.getSelection();

                    var text = '';
                    if (this.oldIE()) text = sel.text;
                    else text = sel.toString();

                    $('#redactor_filename').val(text);

                    // dragupload
                    if (!this.isMobile()) {
                         this.draguploadInit('#redactor_file', {
                              url: this.opts.fileUpload,
                              uploadFields: this.opts.uploadFields,
                              success: $.proxy(this.fileCallback, this),
                              error: $.proxy(function (obj, json) {
                                   this.callback('fileUploadError', json);

                              }, this)
                         });
                    }

                    this.uploadInit('redactor_file', {
                         auto: true,
                         url: this.opts.fileUpload,
                         success: $.proxy(this.fileCallback, this),
                         error: $.proxy(function (obj, json) {
                              this.callback('fileUploadError', json);

                         }, this)
                    });

               }, this);

               this.modalInit(this.opts.curLang.file, this.opts.modal_file, 500, callback);
          },
          fileCallback: function (json) {

               this.selectionRestore();

               if (json !== false) {

                    var text = $('#redactor_filename').val();
                    if (text === '') text = json.filename;

                    var link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';

                    // chrome fix
                    if (this.browser('webkit') && !!this.window.chrome) {
                         link = link + '&nbsp;';
                    }

                    this.execCommand('inserthtml', link, false);

                    var linkmarker = $(this.$editor.find('a#filelink-marker'));
                    if (linkmarker.size() != 0) linkmarker.removeAttr('id');
                    else linkmarker = false;

                    this.sync();

                    // file upload callback
                    this.callback('fileUpload', linkmarker, json);
               }

               this.modalClose();
          },

          // IMAGE
          imageShow: function () {

               this.selectionSave();

               var callback = $.proxy(function () {
                    // json
                    if (this.opts.imageGetJson) {
                         $.getJSON(this.opts.imageGetJson, $.proxy(function (data) {
                              var folders = {}, count = 0;

                              // folders
                              $.each(data, $.proxy(function (key, val) {
                                   if (typeof val.folder !== 'undefined') {
                                        count++;
                                        folders[val.folder] = count;
                                   }

                              }, this));

                              var folderclass = false;
                              $.each(data, $.proxy(function (key, val) {
                                   // title
                                   var thumbtitle = '';
                                   if (typeof val.title !== 'undefined') thumbtitle = val.title;

                                   var folderkey = 0;
                                   if (!$.isEmptyObject(folders) && typeof val.folder !== 'undefined') {
                                        folderkey = folders[val.folder];
                                        if (folderclass === false) folderclass = '.redactorfolder' + folderkey;
                                   }

                                   var img = $('<img src="' + val.thumb + '" class="redactorfolder redactorfolder' + folderkey + '" rel="' + val.image + '" title="' + thumbtitle + '" />');
                                   $('#redactor_image_box').append(img);
                                   $(img).click($.proxy(this.imageThumbClick, this));

                              }, this));

                              // folders
                              if (!$.isEmptyObject(folders)) {
                                   $('.redactorfolder').hide();
                                   $(folderclass).show();

                                   var onchangeFunc = function (e) {
                                        $('.redactorfolder').hide();
                                        $('.redactorfolder' + $(e.target).val()).show();
                                   };

                                   var select = $('<select id="redactor_image_box_select">');
                                   $.each(folders, function (k, v) {
                                        select.append($('<option value="' + v + '">' + k + '</option>'));
                                   });

                                   $('#redactor_image_box').before(select);
                                   select.change(onchangeFunc);
                              }
                         }, this));

                    }
                    else {
                         $('#redactor_tabs').find('a').eq(1).remove();
                    }

                    if (this.opts.imageUpload || this.opts.s3) {
                         // dragupload
                         if (!this.isMobile() && this.opts.s3 === false) {
                              if ($('#redactor_file').length) {
                                   this.draguploadInit('#redactor_file', {
                                        url: this.opts.imageUpload,
                                        uploadFields: this.opts.uploadFields,
                                        success: $.proxy(this.imageCallback, this),
                                        error: $.proxy(function (obj, json) {
                                             this.callback('imageUploadError', json);

                                        }, this)
                                   });
                              }
                         }

                         if (this.opts.s3 === false) {
                              // ajax upload
                              this.uploadInit('redactor_file', {
                                   auto: true,
                                   url: this.opts.imageUpload,
                                   success: $.proxy(this.imageCallback, this),
                                   error: $.proxy(function (obj, json) {
                                        this.callback('imageUploadError', json);

                                   }, this)
                              });
                         }
                         // s3 upload
                         else {
                              $('#redactor_file').on('change.redactor', $.proxy(this.s3handleFileSelect, this));
                         }

                    }
                    else {
                         $('.redactor_tab').hide();
                         if (!this.opts.imageGetJson) {
                              $('#redactor_tabs').remove();
                              $('#redactor_tab3').show();
                         }
                         else {
                              var tabs = $('#redactor_tabs').find('a');
                              tabs.eq(0).remove();
                              tabs.eq(1).addClass('redactor_tabs_act');
                              $('#redactor_tab2').show();
                         }
                    }

                    $('#redactor_upload_btn').click($.proxy(this.imageCallbackLink, this));

                    if (!this.opts.imageUpload && !this.opts.imageGetJson) {
                         setTimeout(function () {
                              $('#redactor_file_link').focus();

                         }, 200);
                    }

               }, this);

               this.modalInit(this.opts.curLang.image, this.opts.modal_image, 610, callback);

          },
          imageEdit: function (image) {
               var $el = image;
               var parent = $el.parent().parent();

               var callback = $.proxy(function () {
                    $('#redactor_file_alt').val($el.attr('alt'));
                    $('#redactor_image_edit_src').attr('href', $el.attr('src'));
                    $('#redactor_form_image_align').val($el.css('float'));

                    if ($(parent).get(0).tagName === 'A') {
                         $('#redactor_file_link').val($(parent).attr('href'));

                         if ($(parent).attr('target') == '_blank') {
                              $('#redactor_link_blank').prop('checked', true);
                         }
                    }

                    $('#redactor_image_delete_btn').click($.proxy(function () {
                         this.imageRemove($el);

                    }, this));

                    $('#redactorSaveBtn').click($.proxy(function () {
                         this.imageSave($el);

                    }, this));

               }, this);

               this.modalInit(this.opts.curLang.image, this.opts.modal_image_edit, 380, callback);

          },
          imageRemove: function (el) {
               var parent = $(el).parent();
               $(el).remove();

               if (parent.length && parent[0].tagName === 'P') {
                    this.$editor.focus();
                    this.selectionStart(parent);
               }

               // delete callback
               this.callback('imageDelete', el);

               this.modalClose();
               this.sync();
          },
          imageSave: function (el) {
               var $el = $(el);
               var parent = $el.parent();

               $el.attr('alt', $('#redactor_file_alt').val());

               var floating = $('#redactor_form_image_align').val();

               if (floating === 'left') {
                    $el.css({ 'float': 'left', 'margin': '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0' });
               }
               else if (floating === 'right') {
                    $el.css({ 'float': 'right', 'margin': '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + '' });
               }
               else {
                    var imageBox = $el.closest('#redactor-image-box');
                    if (imageBox.size() != 0) imageBox.css({ 'float': '', 'margin': '' });
                    $el.css({ 'float': '', 'margin': '' });
               }

               // as link
               var link = $.trim($('#redactor_file_link').val());
               if (link !== '') {
                    var target = false;
                    if ($('#redactor_link_blank').prop('checked')) {
                         target = true;
                    }

                    if (parent.get(0).tagName !== 'A') {
                         var a = $('<a href="' + link + '">' + this.outerHtml(el) + '</a>');

                         if (target) {
                              a.attr('target', '_blank');
                         }

                         $el.replaceWith(a);
                    }
                    else {
                         parent.attr('href', link);
                         if (target) {
                              parent.attr('target', '_blank');
                         }
                         else {
                              parent.removeAttr('target');
                         }
                    }
               }
               else {
                    if (parent.get(0).tagName === 'A') {
                         parent.replaceWith(this.outerHtml(el));
                    }
               }

               this.modalClose();
               this.observeImages();
               this.sync();

          },
          imageResizeHide: function (e) {
               if (e !== false && $(e.target).parent().size() != 0 && $(e.target).parent()[0].id === 'redactor-image-box') {
                    return false;
               }

               var imageBox = this.$editor.find('#redactor-image-box');
               if (imageBox.size() == 0) {
                    return false;
               }

               this.$editor.find('#redactor-image-editter, #redactor-image-resizer').remove();

               var margin = imageBox.css('margin');
               if (margin != '0px') {
                    imageBox.find('img').css('margin', margin);
                    imageBox.css('margin', '');
               }

               imageBox.find('img').css('opacity', '');
               imageBox.replaceWith(function () {
                    return $(this).contents();
               });

               $(document).off('click.redactor-image-resize-hide');
               this.$editor.off('click.redactor-image-resize-hide');
               this.$editor.off('keydown.redactor-image-delete');

               this.sync()

          },
          imageResize: function (image) {
               var $image = $(image);

               $image.on('mousedown', $.proxy(function () {
                    this.imageResizeHide(false);
               }, this));

               $image.on('dragstart', $.proxy(function () {
                    this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function () {
                         setTimeout($.proxy(function () {
                              this.observeImages();
                              this.$editor.off('drop.redactor-image-inside-drop');
                              this.sync();

                         }, this), 1);

                    }, this));
               }, this));

               $image.on('click', $.proxy(function (e) {
                    if (this.$editor.find('#redactor-image-box').size() != 0) {
                         return false;
                    }

                    var clicked = false,
				start_x,
				start_y,
				ratio = $image.width() / $image.height(),
				min_w = 20,
				min_h = 10;

                    var imageResizer = this.imageResizeControls($image);

                    // resize
                    var isResizing = false;
                    imageResizer.on('mousedown', function (e) {
                         isResizing = true;
                         e.preventDefault();

                         ratio = $image.width() / $image.height();

                         start_x = Math.round(e.pageX - $image.eq(0).offset().left);
                         start_y = Math.round(e.pageY - $image.eq(0).offset().top);

                    });

                    $(this.document.body).on('mousemove', $.proxy(function (e) {
                         if (isResizing) {
                              var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
                              var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;

                              var div_h = $image.height();

                              var new_h = parseInt(div_h, 10) + mouse_y;
                              var new_w = Math.round(new_h * ratio);

                              if (new_w > min_w) {
                                   $image.width(new_w);

                                   if (new_w < 100) {
                                        this.imageEditter.css({
                                             marginTop: '-7px',
                                             marginLeft: '-13px',
                                             fontSize: '9px',
                                             padding: '3px 5px'
                                        });
                                   }
                                   else {
                                        this.imageEditter.css({
                                             marginTop: '-11px',
                                             marginLeft: '-18px',
                                             fontSize: '11px',
                                             padding: '7px 10px'
                                        });
                                   }
                              }

                              start_x = Math.round(e.pageX - $image.eq(0).offset().left);
                              start_y = Math.round(e.pageY - $image.eq(0).offset().top);

                              this.sync()
                         }
                    }, this)).on('mouseup', function () {
                         isResizing = false;
                    });


                    this.$editor.on('keydown.redactor-image-delete', $.proxy(function (e) {
                         var key = e.which;

                         if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key) {
                              this.imageResizeHide(false);
                              this.imageRemove($image);
                         }

                    }, this));

                    $(document).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
                    this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));


               }, this));
          },
          imageResizeControls: function ($image) {
               var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
               imageBox.css({
                    position: 'relative',
                    display: 'inline-block',
                    lineHeight: 0,
                    outline: '1px dashed rgba(0, 0, 0, .6)',
                    'float': $image.css('float')
               });
               imageBox.attr('contenteditable', false);

               var margin = $image.css('margin');
               if (margin != '0px') {
                    imageBox.css('margin', margin);
                    $image.css('margin', '');
               }

               $image.css('opacity', .5).after(imageBox);

               // editter
               this.imageEditter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts.curLang.edit + '</span>');
               this.imageEditter.css({
                    position: 'absolute',
                    zIndex: 2,
                    top: '50%',
                    left: '50%',
                    marginTop: '-11px',
                    marginLeft: '-18px',
                    lineHeight: 1,
                    backgroundColor: '#000',
                    color: '#fff',
                    fontSize: '11px',
                    padding: '7px 10px',
                    cursor: 'pointer'
               });
               this.imageEditter.attr('contenteditable', false);
               this.imageEditter.on('click', $.proxy(function () {
                    this.imageEdit($image);
               }, this));
               imageBox.append(this.imageEditter);

               // resizer
               var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
               imageResizer.css({
                    position: 'absolute',
                    zIndex: 2,
                    lineHeight: 1,
                    cursor: 'nw-resize',
                    bottom: '-4px',
                    right: '-5px',
                    border: '1px solid #fff',
                    backgroundColor: '#000',
                    width: '8px',
                    height: '8px'
               });
               imageResizer.attr('contenteditable', false);
               imageBox.append(imageResizer);

               imageBox.append($image);

               return imageResizer;
          },
          imageThumbClick: function (e) {
               var img = '<img id="image-marker" src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '" />';

               if (this.opts.paragraphy) img = '<p>' + img + '</p>';

               this.imageInsert(img, true);
          },
          imageCallbackLink: function () {
               var val = $('#redactor_file_link').val();

               if (val !== '') {
                    var data = '<img id="image-marker" src="' + val + '" />';
                    if (this.opts.linebreaks === false) data = '<p>' + data + '</p>';

                    this.imageInsert(data, true);

               }
               else this.modalClose();
          },
          imageCallback: function (data) {
               this.imageInsert(data);
          },
          imageInsert: function (json, link) {
               this.selectionRestore();

               if (json !== false) {
                    var html = '';
                    if (link !== true) {
                         html = '<img id="image-marker" src="' + json.filelink + '" />';
                         if (this.opts.paragraphy) html = '<p>' + html + '</p>';
                    }
                    else {
                         html = json;
                    }

                    this.execCommand('inserthtml', html, false);

                    var image = $(this.$editor.find('img#image-marker'));

                    if (image.length) image.removeAttr('id');
                    else image = false;

                    this.sync();

                    // upload image callback
                    link !== true && this.callback('imageUpload', image, json);
               }

               this.modalClose();
               this.observeImages();
          },

          // MODAL
          modalTemplatesInit: function () {
               $.extend(this.opts,
			{
			     modal_file: String()
				+ '<section>'
					+ '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
							+ '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
					+ '</div>'
					+ '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
						+ '<label>' + this.opts.curLang.filename + '</label>'
						+ '<input type="text" id="redactor_filename" class="redactor_input" />'
						+ '<div style="margin-top: 7px;">'
							+ '<input type="file" id="redactor_file" name="file" />'
						+ '</div>'
					+ '</form>'
				+ '</section>',

			     modal_image_edit: String()
				+ '<section>'
					+ '<label>' + this.opts.curLang.title + '</label>'
					+ '<input id="redactor_file_alt" class="redactor_input" />'
					+ '<label>' + this.opts.curLang.link + '</label>'
					+ '<input id="redactor_file_link" class="redactor_input" />'
					+ '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
					+ '<label>' + this.opts.curLang.image_position + '</label>'
					+ '<select id="redactor_form_image_align">'
						+ '<option value="none">' + this.opts.curLang.none + '</option>'
						+ '<option value="left">' + this.opts.curLang.left + '</option>'
						+ '<option value="right">' + this.opts.curLang.right + '</option>'
					+ '</select>'
				+ '</section>'
				+ '<footer>'
					+ '<button id="redactor_image_delete_btn" class="redactor_modal_btn">' + this.opts.curLang._delete + '</button>&nbsp;&nbsp;&nbsp;'
					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
					+ '<input type="button" name="save" class="redactor_modal_btn" id="redactorSaveBtn" value="' + this.opts.curLang.save + '" />'
				+ '</footer>',

			     modal_image: String()
				+ '<section>'
					+ '<div id="redactor_tabs">'
						+ '<a href="#" class="redactor_tabs_act">' + this.opts.curLang.upload + '</a>'
						+ '<a href="#">' + this.opts.curLang.choose + '</a>'
						+ '<a href="#">' + this.opts.curLang.link + '</a>'
					+ '</div>'
					+ '<div id="redactor-progress" class="redactor-progress redactor-progress-striped" style="display: none;">'
							+ '<div id="redactor-progress-bar" class="redactor-progress-bar" style="width: 100%;"></div>'
					+ '</div>'
					+ '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
						+ '<div id="redactor_tab1" class="redactor_tab">'
							+ '<input type="file" id="redactor_file" name="file" />'
						+ '</div>'
						+ '<div id="redactor_tab2" class="redactor_tab" style="display: none;">'
							+ '<div id="redactor_image_box"></div>'
						+ '</div>'
					+ '</form>'
					+ '<div id="redactor_tab3" class="redactor_tab" style="display: none;">'
						+ '<label>' + this.opts.curLang.image_web_link + '</label>'
						+ '<input type="text" name="redactor_file_link" id="redactor_file_link" class="redactor_input"  />'
					+ '</div>'
				+ '</section>'
				+ '<footer>'
					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
					+ '<input type="button" name="upload" class="redactor_modal_btn" id="redactor_upload_btn" value="' + this.opts.curLang.insert + '" />'
				+ '</footer>',

			     modal_link: String()
				+ '<section>'
					+ '<form id="redactorInsertLinkForm" method="post" action="">'
						+ '<div id="redactor_tabs">'
							+ '<a href="#" class="redactor_tabs_act">URL</a>'
							+ '<a href="#">Email</a>'
							+ '<a href="#">' + this.opts.curLang.anchor + '</a>'
						+ '</div>'
						+ '<input type="hidden" id="redactor_tab_selected" value="1" />'
						+ '<div class="redactor_tab" id="redactor_tab1">'
							+ '<label>URL</label>'
							+ '<input type="text" id="redactor_link_url" class="redactor_input"  />'
							+ '<label>' + this.opts.curLang.text + '</label>'
							+ '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_url_text" />'
							+ '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
						+ '</div>'
						+ '<div class="redactor_tab" id="redactor_tab2" style="display: none;">'
							+ '<label>Email</label>'
							+ '<input type="text" id="redactor_link_mailto" class="redactor_input" />'
							+ '<label>' + this.opts.curLang.text + '</label>'
							+ '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_mailto_text" />'
						+ '</div>'
						+ '<div class="redactor_tab" id="redactor_tab3" style="display: none;">'
							+ '<label>' + this.opts.curLang.anchor + '</label>'
							+ '<input type="text" class="redactor_input" id="redactor_link_anchor"  />'
							+ '<label>' + this.opts.curLang.text + '</label>'
							+ '<input type="text" class="redactor_input redactor_link_text" id="redactor_link_anchor_text" />'
						+ '</div>'
					+ '</form>'
				+ '</section>'
				+ '<footer>'
					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
					+ '<input type="button" class="redactor_modal_btn" id="redactor_insert_link_btn" value="' + this.opts.curLang.insert + '" />'
				+ '</footer>',

			     modal_table: String()
				+ '<section>'
					+ '<label>' + this.opts.curLang.rows + '</label>'
					+ '<input type="text" size="5" value="2" id="redactor_table_rows" />'
					+ '<label>' + this.opts.curLang.columns + '</label>'
					+ '<input type="text" size="5" value="3" id="redactor_table_columns" />'
				+ '</section>'
				+ '<footer>'
					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
					+ '<input type="button" name="upload" class="redactor_modal_btn" id="redactor_insert_table_btn" value="' + this.opts.curLang.insert + '" />'
				+ '</footer>',

			     modal_video: String()
				+ '<section>'
					+ '<form id="redactorInsertVideoForm">'
						+ '<label>' + this.opts.curLang.video_html_code + '</label>'
						+ '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
					+ '</form>'
				+ '</section>'
				+ '<footer>'
					+ '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
					+ '<input type="button" class="redactor_modal_btn" id="redactor_insert_video_btn" value="' + this.opts.curLang.insert + '" />'
				+ '</footer>'

			});
          },
          modalInit: function (title, content, width, callback) {
               var $redactorModalOverlay = $('#redactor_modal_overlay');

               // modal overlay
               if (!$redactorModalOverlay.length) {
                    this.$overlay = $redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
                    $('body').prepend(this.$overlay);
               }

               if (this.opts.modalOverlay) {
                    $redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
               }

               var $redactorModal = $('#redactor_modal');

               if (!$redactorModal.length) {
                    this.$modal = $redactorModal = $('<div id="redactor_modal" style="display: none;"><div id="redactor_modal_close">&times;</div><header id="redactor_modal_header"></header><div id="redactor_modal_inner"></div></div>');
                    $('body').append(this.$modal);
               }

               $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));

               this.hdlModalClose = $.proxy(function (e) {
                    if (e.keyCode === this.keyCode.ESC) {
                         this.modalClose();
                         return false;
                    }

               }, this);

               $(document).keyup(this.hdlModalClose);
               this.$editor.keyup(this.hdlModalClose);

               // set content
               this.modalcontent = false;
               if (content.indexOf('#') == 0) {
                    this.modalcontent = $(content);
                    $('#redactor_modal_inner').empty().append(this.modalcontent.html());
                    this.modalcontent.html('');

               }
               else {
                    $('#redactor_modal_inner').empty().append(content);
               }

               $redactorModal.find('#redactor_modal_header').html(title);

               // draggable
               if (typeof $.fn.draggable !== 'undefined') {
                    $redactorModal.draggable({ handle: '#redactor_modal_header' });
                    $redactorModal.find('#redactor_modal_header').css('cursor', 'move');
               }

               var $redactor_tabs = $('#redactor_tabs');

               // tabs
               if ($redactor_tabs.length) {
                    var that = this;
                    $redactor_tabs.find('a').each(function (i, s) {
                         i++;
                         $(s).on('click', function (e) {
                              e.preventDefault();

                              $redactor_tabs.find('a').removeClass('redactor_tabs_act');
                              $(this).addClass('redactor_tabs_act');
                              $('.redactor_tab').hide();
                              $('#redactor_tab' + i).show();
                              $('#redactor_tab_selected').val(i);

                              if (that.isMobile() === false) {
                                   var height = $redactorModal.outerHeight();
                                   $redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
                              }
                         });
                    });
               }

               $redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));

               // save scroll
               if (this.opts.autoresize === true) this.saveModalScroll = this.document.body.scrollTop;

               if (this.isMobile() === false) {
                    $redactorModal.css({
                         position: 'fixed',
                         top: '-2000px',
                         left: '50%',
                         width: width + 'px',
                         marginLeft: '-' + (width + 60) / 2 + 'px'
                    }).show();

                    this.modalSaveBodyOveflow = $(document.body).css('overflow');
                    $(document.body).css('overflow', 'hidden');

               }
               else {
                    $redactorModal.css({
                         position: 'fixed',
                         width: '100%',
                         height: '100%',
                         top: '0',
                         left: '0',
                         margin: '0',
                         minHeight: '300px'
                    }).show();
               }

               // callback
               if (typeof callback === 'function') callback();

               if (this.isMobile() === false) {
                    setTimeout(function () {
                         var height = $redactorModal.outerHeight();
                         $redactorModal.css({
                              top: '50%',
                              height: 'auto',
                              minHeight: 'auto',
                              marginTop: '-' + (height + 10) / 2 + 'px'
                         });
                    }, 10);
               }

          },
          modalClose: function () {
               $('#redactor_modal_close').off('click', this.modalClose);
               $('#redactor_modal').fadeOut('fast', $.proxy(function () {
                    var redactorModalInner = $('#redactor_modal_inner');

                    if (this.modalcontent !== false) {
                         this.modalcontent.html(redactorModalInner.html());
                         this.modalcontent = false;
                    }

                    redactorModalInner.html('');

                    if (this.opts.modalOverlay) {
                         $('#redactor_modal_overlay').hide().off('click', this.modalClose);
                    }

                    $(document).unbind('keyup', this.hdlModalClose);
                    this.$editor.unbind('keyup', this.hdlModalClose);

                    this.selectionRestore();

                    // restore scroll
                    if (this.opts.autoresize && this.saveModalScroll) $(this.document.body).scrollTop(this.saveModalScroll);

               }, this));


               if (this.isMobile() === false) {
                    $(document.body).css('overflow', this.modalSaveBodyOveflow ? this.modalSaveBodyOveflow : 'visible');
               }

               return false;
          },
          modalSetTab: function (num) {
               $('.redactor_tab').hide();
               $('#redactor_tabs').find('a').removeClass('redactor_tabs_act').eq(num - 1).addClass('redactor_tabs_act');
               $('#redactor_tab' + num).show();
          },


          // S3
          s3handleFileSelect: function (e) {
               var files = e.target.files;

               for (var i = 0, f; f = files[i]; i++) {
                    this.s3uploadFile(f);
               }
          },
          s3uploadFile: function (file) {
               this.s3executeOnSignedUrl(file, $.proxy(function (signedURL) {
                    this.s3uploadToS3(file, signedURL);
               }, this));
          },
          s3executeOnSignedUrl: function (file, callback) {
               var xhr = new XMLHttpRequest();
               xhr.open('GET', this.opts.s3 + '?name=' + file.name + '&type=' + file.type, true);

               // Hack to pass bytes through unprocessed.
               xhr.overrideMimeType('text/plain; charset=x-user-defined');

               xhr.onreadystatechange = function (e) {
                    if (this.readyState == 4 && this.status == 200) {
                         $('#redactor-progress').fadeIn();
                         callback(decodeURIComponent(this.responseText));
                    }
                    else if (this.readyState == 4 && this.status != 200) {
                         //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
                    }
               };

               xhr.send();
          },
          s3createCORSRequest: function (method, url) {
               var xhr = new XMLHttpRequest();
               if ("withCredentials" in xhr) {
                    xhr.open(method, url, true);
               }
               else if (typeof XDomainRequest != "undefined") {
                    xhr = new XDomainRequest();
                    xhr.open(method, url);
               }
               else {
                    xhr = null;
               }

               return xhr;
          },
          s3uploadToS3: function (file, url) {
               var xhr = this.s3createCORSRequest('PUT', url);
               if (!xhr) {
                    //setProgress(0, 'CORS not supported');
               }
               else {
                    xhr.onload = $.proxy(function () {
                         if (xhr.status == 200) {
                              //setProgress(100, 'Upload completed.');

                              $('#redactor-progress').hide();

                              var s3image = url.split('?');

                              if (!s3image[0]) {
                                   // url parsing is fail
                                   return false;
                              }

                              this.selectionRestore();

                              var html = '';
                              html = '<img id="image-marker" src="' + s3image[0] + '" />';
                              if (this.opts.paragraphy) html = '<p>' + html + '</p>';

                              this.execCommand('inserthtml', html, false);

                              var image = $(this.$editor.find('img#image-marker'));

                              if (image.length) image.removeAttr('id');
                              else image = false;

                              this.sync();

                              // upload image callback
                              this.callback('imageUpload', image, false);

                              this.modalClose();
                              this.observeImages();

                         }
                         else {
                              //setProgress(0, 'Upload error: ' + xhr.status);
                         }
                    }, this);

                    xhr.onerror = function () {
                         //setProgress(0, 'XHR error.');
                    };

                    xhr.upload.onprogress = function (e) {
                         /*
                         if (e.lengthComputable)
                         {
                         var percentLoaded = Math.round((e.loaded / e.total) * 100);
                         setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
                         }
                         */
                    };

                    xhr.setRequestHeader('Content-Type', file.type);
                    xhr.setRequestHeader('x-amz-acl', 'public-read');

                    xhr.send(file);
               }
          },


          // UPLOAD
          uploadInit: function (el, options) {
               this.uploadOptions = {
                    url: false,
                    success: false,
                    error: false,
                    start: false,
                    trigger: false,
                    auto: false,
                    input: false
               };

               $.extend(this.uploadOptions, options);

               var $el = $('#' + el);

               // Test input or form
               if ($el.length && $el[0].tagName === 'INPUT') {
                    this.uploadOptions.input = $el;
                    this.el = $($el[0].form);
               }
               else this.el = $el;

               this.element_action = this.el.attr('action');

               // Auto or trigger
               if (this.uploadOptions.auto) {
                    $(this.uploadOptions.input).change($.proxy(function (e) {
                         this.el.submit(function (e) {
                              return false;
                         });

                         this.uploadSubmit(e);

                    }, this));

               }
               else if (this.uploadOptions.trigger) {
                    $('#' + this.uploadOptions.trigger).click($.proxy(this.uploadSubmit, this));
               }
          },
          uploadSubmit: function (e) {
               $('#redactor-progress').fadeIn();
               this.uploadForm(this.element, this.uploadFrame());
          },
          uploadFrame: function () {
               this.id = 'f' + Math.floor(Math.random() * 99999);

               var d = this.document.createElement('div');
               var iframe = '<iframe style="display:none" id="' + this.id + '" name="' + this.id + '"></iframe>';

               d.innerHTML = iframe;
               $(d).appendTo("body");

               // Start
               if (this.uploadOptions.start) this.uploadOptions.start();

               $('#' + this.id).load($.proxy(this.uploadLoaded, this));

               return this.id;
          },
          uploadForm: function (f, name) {
               if (this.uploadOptions.input) {
                    var formId = 'redactorUploadForm' + this.id,
					fileId = 'redactorUploadFile' + this.id;

                    this.form = $('<form  action="' + this.uploadOptions.url + '" method="POST" target="' + name + '" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data" />');

                    // append hidden fields
                    if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object') {
                         $.each(this.opts.uploadFields, $.proxy(function (k, v) {
                              if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();

                              var hidden = $('<input/>', {
                                   'type': "hidden",
                                   'name': k,
                                   'value': v
                              });

                              $(this.form).append(hidden);

                         }, this));
                    }

                    var oldElement = this.uploadOptions.input;
                    var newElement = $(oldElement).clone();

                    $(oldElement).attr('id', fileId).before(newElement).appendTo(this.form);

                    $(this.form).css('position', 'absolute')
						.css('top', '-2000px')
						.css('left', '-2000px')
						.appendTo('body');

                    this.form.submit();

               }
               else {
                    f.attr('target', name)
					.attr('method', 'POST')
					.attr('enctype', 'multipart/form-data')
					.attr('action', this.uploadOptions.url);

                    this.element.submit();
               }
          },
          uploadLoaded: function () {
               var i = $('#' + this.id)[0], d;

               if (i.contentDocument) d = i.contentDocument;
               else if (i.contentWindow) d = i.contentWindow.document;
               else d = window.frames[this.id].document;

               // Success
               if (this.uploadOptions.success) {
                    $('#redactor-progress').hide();

                    if (typeof d !== 'undefined') {
                         // Remove bizarre <pre> tag wrappers around our json data:
                         var rawString = d.body.innerHTML;
                         var jsonString = rawString.match(/\{(.|\n)*\}/)[0];

                         jsonString = jsonString.replace(/^\[/, '');
                         jsonString = jsonString.replace(/\]$/, '');

                         var json = $.parseJSON(jsonString);

                         if (typeof json.error == 'undefined') this.uploadOptions.success(json);
                         else {
                              this.uploadOptions.error(this, json);
                              this.modalClose();
                         }
                    }
                    else {
                         this.modalClose();
                         alert('Upload failed!');
                    }
               }

               this.el.attr('action', this.element_action);
               this.el.attr('target', '');
          },

          // DRAGUPLOAD
          draguploadInit: function (el, options) {
               this.draguploadOptions = $.extend({
                    url: false,
                    success: false,
                    error: false,
                    preview: false,
                    uploadFields: false,
                    text: this.opts.curLang.drop_file_here,
                    atext: this.opts.curLang.or_choose
               }, options);

               if (window.FormData === undefined) return false;

               this.droparea = $('<div class="redactor_droparea"></div>');
               this.dropareabox = $('<div class="redactor_dropareabox">' + this.draguploadOptions.text + '</div>');
               this.dropalternative = $('<div class="redactor_dropalternative">' + this.draguploadOptions.atext + '</div>');

               this.droparea.append(this.dropareabox);

               $(el).before(this.droparea);
               $(el).before(this.dropalternative);

               // drag over
               this.dropareabox.on('dragover', $.proxy(function () {
                    return this.draguploadOndrag();

               }, this));

               // drag leave
               this.dropareabox.on('dragleave', $.proxy(function () {
                    return this.draguploadOndragleave();

               }, this));

               // drop
               this.dropareabox.get(0).ondrop = $.proxy(function (e) {
                    e.preventDefault();

                    this.dropareabox.removeClass('hover').addClass('drop');

                    this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false);

               }, this);
          },
          dragUploadAjax: function (url, file, directupload, progress, e) {


               if (!directupload) {
                    var xhr = $.ajaxSettings.xhr();
                    if (xhr.upload) {
                         xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
                    }

                    $.ajaxSetup({
                         xhr: function () { return xhr; }
                    });
               }

               var fd = new FormData();

               // append file data
               fd.append('file', file);

               // append hidden fields
               if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object') {
                    $.each(this.opts.uploadFields, $.proxy(function (k, v) {
                         if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
                         fd.append(k, v);

                    }, this));
               }

               $.ajax({
                    url: url,
                    dataType: 'html',
                    data: fd,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',
                    success: $.proxy(function (data) {
                         data = data.replace(/^\[/, '');
                         data = data.replace(/\]$/, '');

                         var json = $.parseJSON(data);

                         if (directupload) {
                              progress.fadeOut('slow', function () {
                                   $(this).remove();
                              });

                              var $img = $('<img>');
                              $img.attr('src', json.filelink).attr('id', 'drag-image-marker');

                              this.insertNodeToCaretPositionFromPoint(e, $img[0]);

                              var image = $(this.$editor.find('img#drag-image-marker'));
                              if (image.length) image.removeAttr('id');
                              else image = false;

                              this.sync();
                              this.observeImages();

                              // upload callback
                              if (image) this.callback('imageUpload', image, json);

                              // error callback
                              if (typeof json.error !== 'undefined') this.callback('imageUploadError', json);
                         }
                         else {
                              if (typeof json.error == 'undefined') {
                                   this.draguploadOptions.success(json);
                              }
                              else {
                                   this.draguploadOptions.error(this, json);
                                   this.draguploadOptions.success(false);
                              }
                         }

                    }, this)
               });
          },
          draguploadOndrag: function () {
               this.dropareabox.addClass('hover');
               return false;
          },
          draguploadOndragleave: function () {
               this.dropareabox.removeClass('hover');
               return false;
          },
          uploadProgress: function (e, text) {
               var percent = e.loaded ? parseInt(e.loaded / e.total * 100, 10) : e;
               this.dropareabox.text('Loading ' + percent + '% ' + (text || ''));
          },


          // UTILS
          isMobile: function () {
               return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
          },
          normalize: function (str) {
               if (typeof (str) === 'undefined') return 0;
               return parseInt(str.replace('px', ''), 10);
          },
          outerHtml: function (el) {
               return $('<div>').append($(el).eq(0).clone()).html();
          },
          isString: function (obj) {
               return Object.prototype.toString.call(obj) == '[object String]';
          },
          isEmpty: function (html) {
               html = html.replace(/&#x200b;|<br>|<br\/>|&nbsp;/gi, '');
               html = html.replace(/\s/g, '');
               html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');


               return html == '';
          },
          browser: function (browser) {
               var ua = navigator.userAgent.toLowerCase();
               var match = /(chrome)[ \/]([\w.]+)/.exec(ua)
					|| /(webkit)[ \/]([\w.]+)/.exec(ua)
					|| /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)
					|| /(msie) ([\w.]+)/.exec(ua)
					|| ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)
					|| [];

               if (browser == 'version') return match[2];
               if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');

               return match[1] == browser;
          },
          oldIE: function () {
               if (this.browser('msie') && parseInt(this.browser('version'), 10) < 9) return true;
               return false;
          },
          getFragmentHtml: function (fragment) {
               var cloned = fragment.cloneNode(true);
               var div = this.document.createElement('div');

               div.appendChild(cloned);
               return div.innerHTML;
          },
          extractContent: function () {
               var node = this.$editor[0];
               var frag = this.document.createDocumentFragment();
               var child;

               while ((child = node.firstChild)) {
                    frag.appendChild(child);
               }

               return frag;
          },
          isParentRedactor: function (el) {
               if (!el) return false;
               if (this.opts.iframe) return el;

               if ($(el).parents('div.redactor_editor').length == 0 || $(el).hasClass('redactor_editor')) return false;
               else return el;
          },
          currentOrParentIs: function (tagName) {
               var parent = this.getParent(), current = this.getCurrent();
               return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
          },
          isEndOfElement: function () {
               var current = this.getBlock();
               var offset = this.getCaretOffset(current);

               var text = $.trim($(current).text()).replace(/\n\r\n/g, '');

               var len = text.length;

               if (offset == len) return true;
               else return false;
          },
          isFocused: function () {
               var el, sel = this.getSelection();

               if (sel && sel.rangeCount && sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
               if (!el) return false;
               if (this.opts.iframe) {
                    if (this.getCaretOffsetRange().equals()) return !this.$editor.is(el);
                    else return true;
               }

               return $(el).closest('div.redactor_editor').length != 0;
          },
          removeEmptyAttr: function (el, attr) {
               if ($(el).attr(attr) == '') $(el).removeAttr(attr);
          },
          removeFromArrayByValue: function (array, value) {
               var index = null;
               if (!array.indexOf) {
                    for (var xx in array) {
                         var curItem = array[xx];
                         if (curItem == value) {
                              array.splice(xx, 1);
                         }
                    }
               }
               else {
                    while ((index = array.indexOf(value)) !== -1) {
                         array.splice(index, 1);
                    }
               }

               return array;
          }
     };

     // constructor
     Redactor.prototype.init.prototype = Redactor.prototype;

     // LINKIFY
     $.Redactor.fn.formatLinkify = function (protocol, convertLinks, convertImageLinks, convertVideoLinks) {
          var url1 = /(^|&lt;|\s)(www\..+?\..+?)(\s|&gt;|$)/g,
			url2 = /(^|&lt;|\s)(((https?|ftp):\/\/|mailto:).+?)(\s|&gt;|$)/g,
			urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi,
			urlYoutube = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/,
			urlVimeo = /http:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;

          var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length;
          while (i--) {
               var n = childNodes[i];
               if (n.nodeType === 3) {
                    var html = n.nodeValue;

                    // youtube & vimeo
                    if (convertVideoLinks && html) {
                         var iframeStart = '<iframe width="500" height="281" src="',
						iframeEnd = '" frameborder="0" allowfullscreen></iframe>';

                         if (html.match(urlYoutube)) {
                              html = html.replace(urlYoutube, iframeStart + '//www.youtube.com/embed/$2' + iframeEnd);
                              $(n).after(html).remove();
                         }
                         else if (html.match(urlVimeo)) {
                              html = html.replace(urlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
                              $(n).after(html).remove();
                         }
                    }

                    // image
                    if (convertImageLinks && html && html.match(urlImage)) {
                         html = html.replace(urlImage, '<img src="$1">');

                         $(n).after(html).remove();
                    }

                    // link
                    if (convertLinks && html && (html.match(url1) || html.match(url2))) {
                         var href = (html.match(url1) || html.match(url2));
                         href = href[0];
                         if (href.length > 50) href = href.substring(0, 50) + '...';

                         html = html.replace(/&/g, '&amp;')
					.replace(/</g, '&lt;')
					.replace(/>/g, '&gt;')
					.replace(url1, '$1<a href="' + protocol + '$2">' + href + '</a>$3')
					.replace(url2, '$1<a href="$2">' + href + '</a>$5');


                         $(n).after(html).remove();
                    }
               }
               else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName)) {
                    $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertImageLinks, convertVideoLinks);
               }
          }
     };

})(jQuery);